Skip to content

Commit dd308fc

Browse files
committed
feat: Support AWS SDK v3
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 syncronus function needs to store enugh state that the _OnDecrypt function can await the setting of the region value. Second in setting this value `readOnlyProperty` was updated to explictly set `writable: false`. This allows this helper to update a property and set this updated value to read only.
1 parent 860709b commit dd308fc

23 files changed

+15326
-10903
lines changed

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

+4-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,16 @@ const getKmsClient = getClient(KMS, {
2930
})
3031
const cacheKmsClients = cacheClients(getKmsClient)
3132

32-
export type KmsKeyringWebCryptoInput = Partial<KmsKeyringInput<KMS>>
33+
export type KmsKeyringWebCryptoInput = Partial<KmsKeyringInput<AwsEsdkKMSInterface>>
3334
export type KMSWebCryptoConstructible = KMSConstructible<
3435
KMS,
3536
KMS.ClientConfiguration
3637
>
37-
export type KmsWebCryptoClientSupplier = KmsClientSupplier<KMS>
38+
export type KmsWebCryptoClientSupplier = KmsClientSupplier<AwsEsdkKMSInterface>
3839

3940
export class KmsKeyringBrowser extends KmsKeyringClass<
4041
WebCryptoAlgorithmSuite,
41-
KMS
42+
AwsEsdkKMSInterface
4243
>(KeyringWebCrypto as Newable<KeyringWebCrypto>) {
4344
constructor({
4445
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

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
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 { getAwsKmsMrkAwareDiscoveryMultiKeyringBuilder, AwsEsdkKMSInterface } from '@aws-crypto/kms-keyring'
55
import {
66
MultiKeyringWebCrypto,
77
WebCryptoAlgorithmSuite,
88
} from '@aws-crypto/material-management-browser'
99
import { getKmsClient } from '.'
1010
import { AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser } from './kms_mrk_discovery_keyring_browser'
11-
import { KMS } from 'aws-sdk'
1211

1312
export const buildAwsKmsMrkAwareDiscoveryMultiKeyringBrowser =
14-
getAwsKmsMrkAwareDiscoveryMultiKeyringBuilder<WebCryptoAlgorithmSuite, KMS>(
13+
getAwsKmsMrkAwareDiscoveryMultiKeyringBuilder<WebCryptoAlgorithmSuite, AwsEsdkKMSInterface>(
1514
AwsKmsMrkAwareSymmetricDiscoveryKeyringBrowser,
1615
MultiKeyringWebCrypto,
1716
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

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
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 { getAwsKmsMrkAwareStrictMultiKeyringBuilder, AwsEsdkKMSInterface } from '@aws-crypto/kms-keyring'
55
import {
66
MultiKeyringWebCrypto,
77
WebCryptoAlgorithmSuite,
88
} from '@aws-crypto/material-management-browser'
99
import { getKmsClient } from '.'
1010
import { AwsKmsMrkAwareSymmetricKeyringBrowser } from './kms_mrk_keyring_browser'
11-
import { KMS } from 'aws-sdk'
1211

1312
export const buildAwsKmsMrkAwareStrictMultiKeyringBrowser =
14-
getAwsKmsMrkAwareStrictMultiKeyringBuilder<WebCryptoAlgorithmSuite, KMS>(
13+
getAwsKmsMrkAwareStrictMultiKeyringBuilder<WebCryptoAlgorithmSuite, AwsEsdkKMSInterface>(
1514
AwsKmsMrkAwareSymmetricKeyringBrowser,
1615
MultiKeyringWebCrypto,
1716
getKmsClient

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

+47-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,13 @@ 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 })
5961
const keyring = new KmsKeyringBrowser({
6062
clientProvider,
6163
generatorKeyId,
@@ -88,3 +90,44 @@ describe('KmsKeyringBrowser encrypt/decrypt', () => {
8890
expect(() => test.getUnencryptedDataKey()).to.throw()
8991
})
9092
})
93+
94+
95+
describe('KmsKeyringBrowser can encrypt/decrypt with AWS SDK v3 client', () => {
96+
const generatorKeyId =
97+
'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'
98+
const keyArn =
99+
'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f'
100+
const keyIds = [keyArn]
101+
const clientProvider = getClient(V3KMS, { credentials })
102+
const keyring = new KmsKeyringBrowser({
103+
clientProvider,
104+
generatorKeyId,
105+
keyIds,
106+
})
107+
let encryptedDataKey: EncryptedDataKey
108+
109+
it('can encrypt and create unencrypted data key', async () => {
110+
const suite = new WebCryptoAlgorithmSuite(
111+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
112+
)
113+
const material = new WebCryptoEncryptionMaterial(suite, {})
114+
const test = await keyring.onEncrypt(material)
115+
expect(test.hasValidKey()).to.equal(true)
116+
const udk = test.getUnencryptedDataKey()
117+
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
118+
expect(test.encryptedDataKeys).to.have.lengthOf(2)
119+
const [edk] = test.encryptedDataKeys
120+
encryptedDataKey = edk
121+
})
122+
123+
it('can decrypt an EncryptedDataKey', async () => {
124+
const suite = new WebCryptoAlgorithmSuite(
125+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
126+
)
127+
const material = new WebCryptoDecryptionMaterial(suite, {})
128+
const test = await keyring.onDecrypt(material, [encryptedDataKey])
129+
expect(test.hasValidKey()).to.equal(true)
130+
// The UnencryptedDataKey should be zeroed, because the cryptoKey has been set
131+
expect(() => test.getUnencryptedDataKey()).to.throw()
132+
})
133+
})

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,12 @@ 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 }),
7172
keyId: eastKeyId,
7273
grantTokens,
7374
})
@@ -112,3 +113,66 @@ describe('AwsKmsMrkAwareSymmetricKeyringBrowser encrypt/decrypt', () => {
112113
expect(() => test.getUnencryptedDataKey()).to.throw()
113114
})
114115
})
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 }),
135+
keyId: eastKeyId,
136+
grantTokens,
137+
})
138+
let encryptedDataKey: EncryptedDataKey
139+
140+
it('can encrypt and create unencrypted data key', async () => {
141+
const material = new WebCryptoEncryptionMaterial(suite, encryptionContext)
142+
const test = await encryptKeyring.onEncrypt(material)
143+
expect(test.hasValidKey()).to.equal(true)
144+
const udk = test.getUnencryptedDataKey()
145+
expect(udk).to.have.lengthOf(suite.keyLengthBytes)
146+
expect(test.encryptedDataKeys).to.have.lengthOf(1)
147+
const [edk] = test.encryptedDataKeys
148+
encryptedDataKey = edk
149+
})
150+
151+
it('can encrypt a pre-existing plaintext data key', async () => {
152+
const seedMaterial = new WebCryptoEncryptionMaterial(
153+
suite,
154+
encryptionContext
155+
).setUnencryptedDataKey(new Uint8Array(suite.keyLengthBytes), {
156+
keyName: 'keyName',
157+
keyNamespace: 'keyNamespace',
158+
flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY,
159+
})
160+
const encryptTest = await encryptKeyring.onEncrypt(seedMaterial)
161+
expect(encryptTest.hasValidKey()).to.equal(true)
162+
expect(encryptTest.encryptedDataKeys).to.have.lengthOf(1)
163+
const [kmsEDK] = encryptTest.encryptedDataKeys
164+
expect(kmsEDK.providerId).to.equal('aws-kms')
165+
expect(kmsEDK.providerInfo).to.equal(westKeyId)
166+
})
167+
168+
it('can decrypt an EncryptedDataKey', async () => {
169+
const suite = new WebCryptoAlgorithmSuite(
170+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA256
171+
)
172+
const material = new WebCryptoDecryptionMaterial(suite, encryptionContext)
173+
const test = await decryptKeyring.onDecrypt(material, [encryptedDataKey])
174+
expect(test.hasValidKey()).to.equal(true)
175+
// The UnencryptedDataKey should be zeroed, because the cryptoKey has been set
176+
expect(() => test.getUnencryptedDataKey()).to.throw()
177+
})
178+
})

0 commit comments

Comments
 (0)