Skip to content

Commit 33a9e43

Browse files
authored
feat: Support AWS SDK v3 (#1043)
To support AWS SDK v3, there needs to be a single interface between AWS SDK v2 and v3 for the AWS KMS Client. The `AwsEsdkKMSInterface` is a close copy that needs to be shared between `kms-keyring`, `kms-keyring-node, and `kms-keyring-browser`. Adding `@aws-sdk/client-kms` as a devDependencies tests have been added for v2 and v3 for every AWS KMS keyring. Two additional changes were required. First, getting the `region` value from an AWS SDK v3 client is an `async` function, rather than a string property. This means that the `AwsKmsMrkAwareSymmetricDiscoveryKeyring` needs to be able to handle this. The constructor, a synchronous function needs to store enough state that the _OnDecrypt function can await the setting of the region value. Second in setting this value `readOnlyProperty` was updated to explicitly set `writable: false`. This allows this helper to update a property and set this updated value to read only.
1 parent fd13828 commit 33a9e43

23 files changed

+15362
-10919
lines changed

modules/kms-keyring-browser/src/kms_keyring_browser.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
limitRegions,
1111
excludeRegions,
1212
cacheClients,
13+
AwsEsdkKMSInterface,
1314
} from '@aws-crypto/kms-keyring'
1415
import {
1516
WebCryptoAlgorithmSuite,
@@ -29,16 +30,18 @@ const getKmsClient = getClient(KMS, {
2930
})
3031
const cacheKmsClients = cacheClients(getKmsClient)
3132

32-
export type KmsKeyringWebCryptoInput = Partial<KmsKeyringInput<KMS>>
33+
export type KmsKeyringWebCryptoInput = Partial<
34+
KmsKeyringInput<AwsEsdkKMSInterface>
35+
>
3336
export type KMSWebCryptoConstructible = KMSConstructible<
3437
KMS,
3538
KMS.ClientConfiguration
3639
>
37-
export type KmsWebCryptoClientSupplier = KmsClientSupplier<KMS>
40+
export type KmsWebCryptoClientSupplier = KmsClientSupplier<AwsEsdkKMSInterface>
3841

3942
export class KmsKeyringBrowser extends KmsKeyringClass<
4043
WebCryptoAlgorithmSuite,
41-
KMS
44+
AwsEsdkKMSInterface
4245
>(KeyringWebCrypto as Newable<KeyringWebCrypto>) {
4346
constructor({
4447
clientProvider = cacheKmsClients,

modules/kms-keyring-browser/src/kms_mrk_discovery_keyring_browser.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import {
55
AwsKmsMrkAwareSymmetricDiscoveryKeyringClass,
66
AwsKmsMrkAwareSymmetricDiscoveryKeyringInput,
7+
AwsEsdkKMSInterface,
78
} from '@aws-crypto/kms-keyring'
89
import {
910
WebCryptoAlgorithmSuite,
@@ -16,16 +17,15 @@ import {
1617
KeyringWebCrypto,
1718
Newable,
1819
} from '@aws-crypto/material-management-browser'
19-
import { KMS } from 'aws-sdk'
2020

2121
export type AwsKmsMrkAwareSymmetricDiscoveryKeyringWebCryptoInput =
22-
AwsKmsMrkAwareSymmetricDiscoveryKeyringInput<KMS>
22+
AwsKmsMrkAwareSymmetricDiscoveryKeyringInput<AwsEsdkKMSInterface>
2323

2424
export class AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser extends AwsKmsMrkAwareSymmetricDiscoveryKeyringClass<
2525
WebCryptoAlgorithmSuite,
26-
KMS
26+
AwsEsdkKMSInterface
2727
>(KeyringWebCrypto as Newable<KeyringWebCrypto>) {
28-
declare client: KMS
28+
declare client: AwsEsdkKMSInterface
2929

3030
constructor({
3131
client,

modules/kms-keyring-browser/src/kms_mrk_discovery_multi_keyring_browser.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { getAwsKmsMrkAwareDiscoveryMultiKeyringBuilder } from '@aws-crypto/kms-keyring'
4+
import {
5+
getAwsKmsMrkAwareDiscoveryMultiKeyringBuilder,
6+
AwsEsdkKMSInterface,
7+
} from '@aws-crypto/kms-keyring'
58
import {
69
MultiKeyringWebCrypto,
710
WebCryptoAlgorithmSuite,
811
} from '@aws-crypto/material-management-browser'
912
import { getKmsClient } from '.'
1013
import { AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser } from './kms_mrk_discovery_keyring_browser'
11-
import { KMS } from 'aws-sdk'
1214

1315
export const buildAwsKmsMrkAwareDiscoveryMultiKeyringBrowser =
14-
getAwsKmsMrkAwareDiscoveryMultiKeyringBuilder<WebCryptoAlgorithmSuite, KMS>(
16+
getAwsKmsMrkAwareDiscoveryMultiKeyringBuilder<
17+
WebCryptoAlgorithmSuite,
18+
AwsEsdkKMSInterface
19+
>(
1520
AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser,
1621
MultiKeyringWebCrypto,
1722
getKmsClient

modules/kms-keyring-browser/src/kms_mrk_keyring_browser.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import {
55
AwsKmsMrkAwareSymmetricKeyringClass,
66
AwsKmsMrkAwareSymmetricKeyringInput,
7+
AwsEsdkKMSInterface,
78
} from '@aws-crypto/kms-keyring'
89
import {
910
WebCryptoAlgorithmSuite,
@@ -16,16 +17,15 @@ import {
1617
KeyringWebCrypto,
1718
Newable,
1819
} from '@aws-crypto/material-management-browser'
19-
import { KMS } from 'aws-sdk'
2020

2121
export type AwsKmsMrkAwareSymmetricKeyringWebCryptoInput =
22-
AwsKmsMrkAwareSymmetricKeyringInput<KMS>
22+
AwsKmsMrkAwareSymmetricKeyringInput<AwsEsdkKMSInterface>
2323

2424
export class AwsKmsMrkAwareSymmetricKeyringBrowser extends AwsKmsMrkAwareSymmetricKeyringClass<
2525
WebCryptoAlgorithmSuite,
26-
KMS
26+
AwsEsdkKMSInterface
2727
>(KeyringWebCrypto as Newable<KeyringWebCrypto>) {
28-
declare client: KMS
28+
declare client: AwsEsdkKMSInterface
2929
declare keyId: string
3030
declare grantTokens?: string[]
3131

Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { getAwsKmsMrkAwareStrictMultiKeyringBuilder } from '@aws-crypto/kms-keyring'
4+
import {
5+
getAwsKmsMrkAwareStrictMultiKeyringBuilder,
6+
AwsEsdkKMSInterface,
7+
} from '@aws-crypto/kms-keyring'
58
import {
69
MultiKeyringWebCrypto,
710
WebCryptoAlgorithmSuite,
811
} from '@aws-crypto/material-management-browser'
912
import { getKmsClient } from '.'
1013
import { AwsKmsMrkAwareSymmetricKeyringBrowser } from './kms_mrk_keyring_browser'
11-
import { KMS } from 'aws-sdk'
1214

1315
export const buildAwsKmsMrkAwareStrictMultiKeyringBrowser =
14-
getAwsKmsMrkAwareStrictMultiKeyringBuilder<WebCryptoAlgorithmSuite, KMS>(
15-
AwsKmsMrkAwareSymmetricKeyringBrowser,
16-
MultiKeyringWebCrypto,
17-
getKmsClient
18-
)
16+
getAwsKmsMrkAwareStrictMultiKeyringBuilder<
17+
WebCryptoAlgorithmSuite,
18+
AwsEsdkKMSInterface
19+
>(AwsKmsMrkAwareSymmetricKeyringBrowser, MultiKeyringWebCrypto, getKmsClient)

modules/kms-keyring-browser/test/kms_keyring_browser.test.ts

+46-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
import * as chai from 'chai'
77
import chaiAsPromised from 'chai-as-promised'
8-
import { KmsKeyringBrowser, getClient, KMS } from '../src/index'
8+
import { KmsKeyringBrowser, getClient } from '../src/index'
9+
import { KMS as V2KMS } from 'aws-sdk'
10+
import { KMS as V3KMS } from '@aws-sdk/client-kms'
911
import {
1012
KeyringWebCrypto,
1113
WebCryptoEncryptionMaterial,
@@ -28,7 +30,7 @@ describe('KmsKeyringBrowser::constructor', () => {
2830
const keyArn =
2931
'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f'
3032
const keyIds = [keyArn]
31-
const clientProvider = getClient(KMS, { credentials })
33+
const clientProvider = getClient(V2KMS, { credentials })
3234

3335
const test = new KmsKeyringBrowser({
3436
clientProvider,
@@ -49,13 +51,53 @@ describe('KmsKeyringBrowser::constructor', () => {
4951
})
5052
})
5153

52-
describe('KmsKeyringBrowser encrypt/decrypt', () => {
54+
describe('KmsKeyringBrowser can encrypt/decrypt with AWS SDK v2 client', () => {
5355
const generatorKeyId =
5456
'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'
5557
const keyArn =
5658
'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f'
5759
const keyIds = [keyArn]
58-
const clientProvider = getClient(KMS, { credentials })
60+
const clientProvider = getClient(V2KMS, { credentials })
61+
const keyring = new KmsKeyringBrowser({
62+
clientProvider,
63+
generatorKeyId,
64+
keyIds,
65+
})
66+
let encryptedDataKey: EncryptedDataKey
67+
68+
it('can encrypt and create unencrypted data key', async () => {
69+
const suite = new WebCryptoAlgorithmSuite(
70+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
71+
)
72+
const material = new WebCryptoEncryptionMaterial(suite, {})
73+
const test = await keyring.onEncrypt(material)
74+
expect(test.hasValidKey()).to.equal(true)
75+
const udk = test.getUnencryptedDataKey()
76+
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
77+
expect(test.encryptedDataKeys).to.have.lengthOf(2)
78+
const [edk] = test.encryptedDataKeys
79+
encryptedDataKey = edk
80+
})
81+
82+
it('can decrypt an EncryptedDataKey', async () => {
83+
const suite = new WebCryptoAlgorithmSuite(
84+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
85+
)
86+
const material = new WebCryptoDecryptionMaterial(suite, {})
87+
const test = await keyring.onDecrypt(material, [encryptedDataKey])
88+
expect(test.hasValidKey()).to.equal(true)
89+
// The UnencryptedDataKey should be zeroed, because the cryptoKey has been set
90+
expect(() => test.getUnencryptedDataKey()).to.throw()
91+
})
92+
})
93+
94+
describe('KmsKeyringBrowser can encrypt/decrypt with AWS SDK v3 client', () => {
95+
const generatorKeyId =
96+
'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'
97+
const keyArn =
98+
'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f'
99+
const keyIds = [keyArn]
100+
const clientProvider = getClient(V3KMS, { credentials })
59101
const keyring = new KmsKeyringBrowser({
60102
clientProvider,
61103
generatorKeyId,

modules/kms-keyring-browser/test/kms_mrk_discovery_keyring_browser.test.ts

+53-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
AlgorithmSuiteIdentifier,
1717
WebCryptoDecryptionMaterial,
1818
} from '@aws-crypto/material-management-browser'
19-
import { KMS } from 'aws-sdk'
19+
import { KMS as V2KMS } from 'aws-sdk'
20+
import { KMS as V3KMS } from '@aws-sdk/client-kms'
2021

2122
chai.use(chaiAsPromised)
2223
const { expect } = chai
@@ -56,7 +57,7 @@ describe('AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser::constructor', () => {
5657
/* Injected from @aws-sdk/karma-credential-loader. */
5758
declare const credentials: any
5859

59-
describe('AwsKmsMrkAwareSymmetricKeyringBrowser encrypt/decrypt', () => {
60+
describe('AwsKmsMrkAwareSymmetricKeyringBrowser can encrypt/decrypt with AWS SDK v2 client', () => {
6061
const discoveryFilter = { accountIDs: ['658956600833'], partition: 'aws' }
6162

6263
const eastKeyId =
@@ -69,7 +70,7 @@ describe('AwsKmsMrkAwareSymmetricKeyringBrowser encrypt/decrypt', () => {
6970

7071
const keyring = new AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser({
7172
// Note the difference in the region from the keyId
72-
client: new KMS({ region: 'us-west-2', credentials }),
73+
client: new V2KMS({ region: 'us-west-2', credentials }),
7374
discoveryFilter,
7475
grantTokens,
7576
})
@@ -84,7 +85,55 @@ describe('AwsKmsMrkAwareSymmetricKeyringBrowser encrypt/decrypt', () => {
8485

8586
it('can decrypt an EncryptedDataKey', async () => {
8687
const encryptKeyring = new AwsKmsMrkAwareSymmetricKeyringBrowser({
87-
client: new KMS({ region: 'us-east-1', credentials }),
88+
client: new V2KMS({ region: 'us-east-1', credentials }),
89+
keyId: eastKeyId,
90+
grantTokens,
91+
})
92+
const encryptMaterial = await encryptKeyring.onEncrypt(
93+
new WebCryptoEncryptionMaterial(suite, encryptionContext)
94+
)
95+
const [edk] = encryptMaterial.encryptedDataKeys
96+
97+
const material = await keyring.onDecrypt(
98+
new WebCryptoDecryptionMaterial(suite, encryptionContext),
99+
[edk]
100+
)
101+
const test = await keyring.onDecrypt(material, [edk])
102+
expect(test.hasValidKey()).to.equal(true)
103+
// The UnencryptedDataKey should be zeroed, because the cryptoKey has been set
104+
expect(() => test.getUnencryptedDataKey()).to.throw()
105+
})
106+
})
107+
108+
describe('AwsKmsMrkAwareSymmetricKeyringBrowser can encrypt/decrypt with AWS SDK v3 client', () => {
109+
const discoveryFilter = { accountIDs: ['658956600833'], partition: 'aws' }
110+
111+
const eastKeyId =
112+
'arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7'
113+
const grantTokens = ['grant']
114+
const encryptionContext = { some: 'context' }
115+
const suite = new WebCryptoAlgorithmSuite(
116+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
117+
)
118+
119+
const keyring = new AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser({
120+
// Note the difference in the region from the keyId
121+
client: new V3KMS({ region: 'us-west-2', credentials }),
122+
discoveryFilter,
123+
grantTokens,
124+
})
125+
126+
it('throws an error on encrypt', async () => {
127+
const material = new WebCryptoEncryptionMaterial(suite, encryptionContext)
128+
return expect(keyring.onEncrypt(material)).to.rejectedWith(
129+
Error,
130+
'AwsKmsMrkAwareSymmetricDiscoveryKeyring cannot be used to encrypt'
131+
)
132+
})
133+
134+
it('can decrypt an EncryptedDataKey', async () => {
135+
const encryptKeyring = new AwsKmsMrkAwareSymmetricKeyringBrowser({
136+
client: new V3KMS({ region: 'us-east-1', credentials }),
88137
keyId: eastKeyId,
89138
grantTokens,
90139
})

modules/kms-keyring-browser/test/kms_mrk_keyring_browser.test.ts

+68-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
WebCryptoDecryptionMaterial,
1616
KeyringTraceFlag,
1717
} from '@aws-crypto/material-management-browser'
18-
import { KMS } from 'aws-sdk'
18+
import { KMS as V2KMS } from 'aws-sdk'
19+
import { KMS as V3KMS } from '@aws-sdk/client-kms'
1920

2021
chai.use(chaiAsPromised)
2122
const { expect } = chai
@@ -50,7 +51,7 @@ describe('AwsKmsMrkAwareSymmetricKeyringBrowser::constructor', () => {
5051
/* Injected from @aws-sdk/karma-credential-loader. */
5152
declare const credentials: any
5253

53-
describe('AwsKmsMrkAwareSymmetricKeyringBrowser encrypt/decrypt', () => {
54+
describe('AwsKmsMrkAwareSymmetricKeyringBrowser can encrypt/decrypt with AWS SDK v2 client', () => {
5455
const westKeyId =
5556
'arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7'
5657
const eastKeyId =
@@ -62,12 +63,75 @@ describe('AwsKmsMrkAwareSymmetricKeyringBrowser encrypt/decrypt', () => {
6263
)
6364

6465
const encryptKeyring = new AwsKmsMrkAwareSymmetricKeyringBrowser({
65-
client: new KMS({ region: 'us-west-2', credentials }),
66+
client: new V2KMS({ region: 'us-west-2', credentials }),
6667
keyId: westKeyId,
6768
grantTokens,
6869
})
6970
const decryptKeyring = new AwsKmsMrkAwareSymmetricKeyringBrowser({
70-
client: new KMS({ region: 'us-east-1', credentials }),
71+
client: new V2KMS({ region: 'us-east-1', credentials }),
72+
keyId: eastKeyId,
73+
grantTokens,
74+
})
75+
let encryptedDataKey: EncryptedDataKey
76+
77+
it('can encrypt and create unencrypted data key', async () => {
78+
const material = new WebCryptoEncryptionMaterial(suite, encryptionContext)
79+
const test = await encryptKeyring.onEncrypt(material)
80+
expect(test.hasValidKey()).to.equal(true)
81+
const udk = test.getUnencryptedDataKey()
82+
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
83+
expect(test.encryptedDataKeys).to.have.lengthOf(1)
84+
const [edk] = test.encryptedDataKeys
85+
encryptedDataKey = edk
86+
})
87+
88+
it('can encrypt a pre-existing plaintext data key', async () => {
89+
const seedMaterial = new WebCryptoEncryptionMaterial(
90+
suite,
91+
encryptionContext
92+
).setUnencryptedDataKey(new Uint8Array(suite.keyLengthBytes), {
93+
keyName: 'keyName',
94+
keyNamespace: 'keyNamespace',
95+
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY,
96+
})
97+
const encryptTest = await encryptKeyring.onEncrypt(seedMaterial)
98+
expect(encryptTest.hasValidKey()).to.equal(true)
99+
expect(encryptTest.encryptedDataKeys).to.have.lengthOf(1)
100+
const [kmsEDK] = encryptTest.encryptedDataKeys
101+
expect(kmsEDK.providerId).to.equal('aws-kms')
102+
expect(kmsEDK.providerInfo).to.equal(westKeyId)
103+
})
104+
105+
it('can decrypt an EncryptedDataKey', async () => {
106+
const suite = new WebCryptoAlgorithmSuite(
107+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
108+
)
109+
const material = new WebCryptoDecryptionMaterial(suite, encryptionContext)
110+
const test = await decryptKeyring.onDecrypt(material, [encryptedDataKey])
111+
expect(test.hasValidKey()).to.equal(true)
112+
// The UnencryptedDataKey should be zeroed, because the cryptoKey has been set
113+
expect(() => test.getUnencryptedDataKey()).to.throw()
114+
})
115+
})
116+
117+
describe('AwsKmsMrkAwareSymmetricKeyringBrowser can encrypt/decrypt with AWS SDK v3 client', () => {
118+
const westKeyId =
119+
'arn:aws:kms:us-west-2:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7'
120+
const eastKeyId =
121+
'arn:aws:kms:us-east-1:658956600833:key/mrk-80bd8ecdcd4342aebd84b7dc9da498a7'
122+
const grantTokens = ['grant']
123+
const encryptionContext = { some: 'context' }
124+
const suite = new WebCryptoAlgorithmSuite(
125+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
126+
)
127+
128+
const encryptKeyring = new AwsKmsMrkAwareSymmetricKeyringBrowser({
129+
client: new V3KMS({ region: 'us-west-2', credentials }),
130+
keyId: westKeyId,
131+
grantTokens,
132+
})
133+
const decryptKeyring = new AwsKmsMrkAwareSymmetricKeyringBrowser({
134+
client: new V3KMS({ region: 'us-east-1', credentials }),
71135
keyId: eastKeyId,
72136
grantTokens,
73137
})

0 commit comments

Comments
 (0)