Skip to content

Commit d73d50d

Browse files
authored
fix: aws sdk version dependencies pollution (#145)
resolves #136 resolved #138 The AWS SDK for JS v2 and v3 have similar types, but they are not exactly the same. In a effort to have both versions work, I imported as dev dependencies all the relevant packages and tried to use conditional types. However, since the types are all exported by the functions this did not work as planed and caused _every_ client to want _every_ SDK. The interface the AWS Encryption SDK for JS needs from the AWS SDK is minimal. Hand rolling the appropriate types is the best solution at this time.
1 parent 9dfa857 commit d73d50d

File tree

6 files changed

+113
-141
lines changed

6 files changed

+113
-141
lines changed

modules/kms-keyring-node/src/kms_keyring_node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ export class KmsKeyringNode extends KmsKeyringClass(KeyringNode as KeyRingConstr
4949
}
5050
immutableClass(KmsKeyringNode)
5151

52-
export { getKmsClient, cacheKmsClients, limitRegions, excludeRegions, cacheClients }
52+
export { getKmsClient, cacheKmsClients, getClient, limitRegions, excludeRegions, cacheClients, KMS }

modules/kms-keyring/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,15 @@
1717
"license": "Apache-2.0",
1818
"dependencies": {
1919
"@aws-crypto/material-management": "^0.2.0-preview.1",
20-
"@aws-sdk/types": "0.1.0-preview.1",
2120
"tslib": "^1.9.3"
2221
},
2322
"devDependencies": {
24-
"@aws-sdk/client-kms-browser": "^0.1.0-preview.2",
25-
"@aws-sdk/client-kms-node": "^0.1.0-preview.2",
2623
"@types/chai": "^4.1.4",
2724
"@types/chai-as-promised": "^7.1.0",
2825
"@types/mocha": "^5.2.5",
2926
"@types/node": "^11.11.4",
3027
"@typescript-eslint/eslint-plugin": "^1.9.0",
3128
"@typescript-eslint/parser": "^1.9.0",
32-
"aws-sdk": "^2.443.0",
3329
"chai": "^4.1.2",
3430
"chai-as-promised": "^7.1.1",
3531
"mocha": "^5.2.0",

modules/kms-keyring/src/helpers.ts

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,13 @@
1515

1616
import { KmsClientSupplier } from './kms_client_supplier' // eslint-disable-line no-unused-vars
1717
import {
18-
KMS, // eslint-disable-line no-unused-vars
19-
GenerateDataKeyInput, // eslint-disable-line no-unused-vars
20-
EncryptInput, // eslint-disable-line no-unused-vars
21-
DecryptInput, // eslint-disable-line no-unused-vars
22-
GenerateDataKeyOutput, // eslint-disable-line no-unused-vars
23-
RequiredGenerateDataKeyOutput, // eslint-disable-line no-unused-vars
24-
EncryptOutput, // eslint-disable-line no-unused-vars
25-
RequiredEncryptOutput, // eslint-disable-line no-unused-vars
26-
DecryptOutput, // eslint-disable-line no-unused-vars
27-
RequiredDecryptOutput // eslint-disable-line no-unused-vars
18+
AwsEsdkKMSInterface, // eslint-disable-line no-unused-vars
19+
GenerateDataKeyResponse, // eslint-disable-line no-unused-vars
20+
RequiredGenerateDataKeyResponse, // eslint-disable-line no-unused-vars
21+
EncryptResponse, // eslint-disable-line no-unused-vars
22+
RequiredEncryptResponse, // eslint-disable-line no-unused-vars
23+
DecryptResponse, // eslint-disable-line no-unused-vars
24+
RequiredDecryptResponse // eslint-disable-line no-unused-vars
2825
} from './kms_types'
2926
import { regionFromKmsKeyArn } from './region_from_kms_key_arn'
3027
import {
@@ -35,113 +32,110 @@ import {
3532

3633
export const KMS_PROVIDER_ID = 'aws-kms'
3734

38-
export async function generateDataKey<Client extends KMS> (
35+
export async function generateDataKey<Client extends AwsEsdkKMSInterface> (
3936
clientProvider: KmsClientSupplier<Client>,
4037
NumberOfBytes: number,
4138
KeyId: string,
4239
EncryptionContext?: EncryptionContext,
4340
GrantTokens?: string[]
44-
): Promise<RequiredGenerateDataKeyOutput|false> {
41+
): Promise<RequiredGenerateDataKeyResponse|false> {
4542
const region = regionFromKmsKeyArn(KeyId)
4643
const client = clientProvider(region)
4744

4845
/* Check for early return (Postcondition): Client region was not provided. */
4946
if (!client) return false
50-
const request: GenerateDataKeyInput<Client> = { KeyId, GrantTokens, NumberOfBytes, EncryptionContext } as GenerateDataKeyInput<Client>
51-
// @ts-ignore
52-
const v2vsV3Response = client.generateDataKey(request)
53-
const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response)
54-
const dataKey: GenerateDataKeyOutput<Client> = await v2vsV3Promise
47+
const v2vsV3Response = client.generateDataKey({ KeyId, GrantTokens, NumberOfBytes, EncryptionContext })
48+
const v2vsV3Promise = 'promise' in v2vsV3Response
49+
? v2vsV3Response.promise()
50+
: v2vsV3Response
51+
const dataKey = await v2vsV3Promise
5552

5653
return safeGenerateDataKey(dataKey)
5754
}
5855

59-
export async function encrypt<Client extends KMS> (
56+
export async function encrypt<Client extends AwsEsdkKMSInterface> (
6057
clientProvider: KmsClientSupplier<Client>,
6158
Plaintext: Uint8Array,
6259
KeyId: string,
6360
EncryptionContext?: EncryptionContext,
6461
GrantTokens?: string[]
65-
): Promise<RequiredEncryptOutput|false> {
62+
): Promise<RequiredEncryptResponse|false> {
6663
const region = regionFromKmsKeyArn(KeyId)
6764
const client = clientProvider(region)
6865

6966
/* Check for early return (Postcondition): Client region was not provided. */
7067
if (!client) return false
7168

72-
const request: EncryptInput<Client> = { KeyId, Plaintext, EncryptionContext, GrantTokens } as EncryptInput<Client>
73-
// @ts-ignore
74-
const v2vsV3Response = client.encrypt(request)
75-
const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response)
76-
const kmsEDK: EncryptOutput<Client> = await v2vsV3Promise
69+
const v2vsV3Response = client.encrypt({ KeyId, Plaintext, EncryptionContext, GrantTokens })
70+
const v2vsV3Promise = 'promise' in v2vsV3Response
71+
? v2vsV3Response.promise()
72+
: v2vsV3Response
73+
const kmsEDK = await v2vsV3Promise
7774

7875
return safeEncryptOutput(kmsEDK)
7976
}
8077

81-
export async function decrypt<Client extends KMS> (
78+
export async function decrypt<Client extends AwsEsdkKMSInterface> (
8279
clientProvider: KmsClientSupplier<Client>,
8380
{ providerId, providerInfo, encryptedDataKey }: EncryptedDataKey,
8481
EncryptionContext?: EncryptionContext,
8582
GrantTokens?: string[]
86-
): Promise<RequiredDecryptOutput|false> {
83+
): Promise<RequiredDecryptResponse|false> {
8784
/* Precondition: The EDK must be a KMS edk. */
8885
needs(providerId === KMS_PROVIDER_ID, 'Unsupported providerId')
8986
const region = regionFromKmsKeyArn(providerInfo)
9087
const client = clientProvider(region)
9188
/* Check for early return (Postcondition): Client region was not provided. */
9289
if (!client) return false
9390

94-
const request: DecryptInput<Client> = {
95-
CiphertextBlob: encryptedDataKey,
96-
EncryptionContext,
97-
GrantTokens } as DecryptInput<Client>
98-
// @ts-ignore
99-
const v2vsV3Response = client.decrypt(request)
100-
const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response)
101-
const dataKey: DecryptOutput<Client> = await v2vsV3Promise
91+
const v2vsV3Response = client.decrypt({ CiphertextBlob: encryptedDataKey, EncryptionContext, GrantTokens })
92+
const v2vsV3Promise = 'promise' in v2vsV3Response
93+
? v2vsV3Response.promise()
94+
: v2vsV3Response
95+
const dataKey = await v2vsV3Promise
10296

10397
return safeDecryptOutput(dataKey)
10498
}
10599

106100
export function kmsResponseToEncryptedDataKey ({
107101
KeyId: providerInfo,
108102
CiphertextBlob: encryptedDataKey
109-
}: RequiredEncryptOutput) {
103+
}: RequiredEncryptResponse) {
110104
return new EncryptedDataKey({ providerId: KMS_PROVIDER_ID, providerInfo, encryptedDataKey })
111105
}
112106

113-
function safeGenerateDataKey<Client extends KMS> (
114-
dataKey: GenerateDataKeyOutput<Client>
115-
): RequiredGenerateDataKeyOutput {
107+
function safeGenerateDataKey (
108+
dataKey: GenerateDataKeyResponse
109+
): RequiredGenerateDataKeyResponse {
116110
/* Postcondition: KMS must return serializable generate data key. */
117111
needs(typeof dataKey.KeyId === 'string' &&
118112
dataKey.Plaintext instanceof Uint8Array &&
119113
dataKey.CiphertextBlob instanceof Uint8Array, 'Malformed KMS response.')
120114

121-
return <RequiredGenerateDataKeyOutput>safePlaintext(<RequiredGenerateDataKeyOutput>dataKey)
115+
return <RequiredGenerateDataKeyResponse>safePlaintext(<RequiredGenerateDataKeyResponse>dataKey)
122116
}
123117

124-
function safeEncryptOutput<Client extends KMS> (
125-
dataKey: EncryptOutput<Client>
126-
): RequiredEncryptOutput {
118+
function safeEncryptOutput (
119+
dataKey: EncryptResponse
120+
): RequiredEncryptResponse {
127121
/* Postcondition: KMS must return serializable encrypted data key. */
128122
needs(typeof dataKey.KeyId === 'string' &&
129123
dataKey.CiphertextBlob instanceof Uint8Array, 'Malformed KMS response.')
130124

131-
return <RequiredEncryptOutput>dataKey
125+
return <RequiredEncryptResponse>dataKey
132126
}
133127

134-
function safeDecryptOutput<Client extends KMS> (
135-
dataKey: DecryptOutput<Client>
136-
): RequiredDecryptOutput {
128+
function safeDecryptOutput (
129+
dataKey: DecryptResponse
130+
): RequiredDecryptResponse {
137131
/* Postcondition: KMS must return usable decrypted key. */
138132
needs(typeof dataKey.KeyId === 'string' &&
139133
dataKey.Plaintext instanceof Uint8Array, 'Malformed KMS response.')
140134

141-
return <RequiredDecryptOutput>safePlaintext(<RequiredDecryptOutput>dataKey)
135+
return <RequiredDecryptResponse>safePlaintext(<RequiredDecryptResponse>dataKey)
142136
}
143137

144-
function safePlaintext (dataKey: RequiredDecryptOutput | RequiredGenerateDataKeyOutput): RequiredDecryptOutput | RequiredGenerateDataKeyOutput {
138+
function safePlaintext (dataKey: RequiredDecryptResponse | RequiredGenerateDataKeyResponse): RequiredDecryptResponse | RequiredGenerateDataKeyResponse {
145139
/* The KMS Client *may* return a Buffer that is not isolated.
146140
* i.e. the byteOffset !== 0.
147141
* This means that the unencrypted data key is possibly accessible to someone else.

modules/kms-keyring/src/kms_client_supplier.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,25 @@
1515

1616
import { needs } from '@aws-crypto/material-management'
1717
import {
18-
KMS, // eslint-disable-line no-unused-vars
19-
KMSConfiguration, // eslint-disable-line no-unused-vars
20-
KMSOperations // eslint-disable-line no-unused-vars
18+
AwsEsdkKMSInterface // eslint-disable-line no-unused-vars
2119
} from './kms_types'
2220

23-
interface KMSConstructibleNonOption<Client extends KMS, Config extends KMSConfiguration> {
21+
interface KMSConstructibleNonOption<Client extends AwsEsdkKMSInterface, Config> {
2422
new(config: Config) : Client
2523
}
2624

27-
interface KMSConstructibleOption<Client extends KMS, Config extends KMSConfiguration> {
25+
interface KMSConstructibleOption<Client extends AwsEsdkKMSInterface, Config> {
2826
new(config?: Config) : Client
2927
}
3028

31-
export type KMSConstructible<Client extends KMS, Config extends KMSConfiguration> = KMSConstructibleNonOption<Client, Config> | KMSConstructibleOption<Client, Config>
29+
export type KMSConstructible<Client extends AwsEsdkKMSInterface, Config> = KMSConstructibleNonOption<Client, Config> | KMSConstructibleOption<Client, Config>
3230

33-
export interface KmsClientSupplier<Client extends KMS> {
31+
export interface KmsClientSupplier<Client extends AwsEsdkKMSInterface> {
3432
/* KmsClientProvider is allowed to return undefined if, for example, user wants to exclude particular regions. */
3533
(region: string): Client | false
3634
}
3735

38-
export function getClient<Client extends KMS, Config extends KMSConfiguration> (
36+
export function getClient<Client extends AwsEsdkKMSInterface, Config> (
3937
KMSClient: KMSConstructible<Client, Config>,
4038
defaultConfig?: Config
4139
): KmsClientSupplier<Client> {
@@ -58,7 +56,7 @@ export function getClient<Client extends KMS, Config extends KMSConfiguration> (
5856
}
5957
}
6058

61-
export function limitRegions<Client extends KMS> (
59+
export function limitRegions<Client extends AwsEsdkKMSInterface> (
6260
regions: string[],
6361
getClient: KmsClientSupplier<Client>
6462
): KmsClientSupplier<Client> {
@@ -71,7 +69,7 @@ export function limitRegions<Client extends KMS> (
7169
}
7270
}
7371

74-
export function excludeRegions<Client extends KMS> (
72+
export function excludeRegions<Client extends AwsEsdkKMSInterface> (
7573
regions: string[],
7674
getClient: KmsClientSupplier<Client>
7775
): KmsClientSupplier<Client> {
@@ -84,7 +82,7 @@ export function excludeRegions<Client extends KMS> (
8482
}
8583
}
8684

87-
export function cacheClients<Client extends KMS> (
85+
export function cacheClients<Client extends AwsEsdkKMSInterface> (
8886
getClient: KmsClientSupplier<Client>
8987
): KmsClientSupplier<Client> {
9088
const clientsCache: {[key: string]: Client|false} = {}
@@ -103,7 +101,7 @@ export function cacheClients<Client extends KMS> (
103101
* This does *not* mean that this call is successful,
104102
* only that the region is backed by a functional KMS service.
105103
*/
106-
function deferCache<Client extends KMS> (
104+
function deferCache<Client extends AwsEsdkKMSInterface> (
107105
clientsCache: {[key: string]: Client|false},
108106
region: string,
109107
client: Client|false
@@ -115,17 +113,17 @@ function deferCache<Client extends KMS> (
115113
}
116114
const { encrypt, decrypt, generateDataKey } = client
117115

118-
return (<KMSOperations[]>['encrypt', 'decrypt', 'generateDataKey']).reduce(wrapOperation, client)
116+
return (<(keyof AwsEsdkKMSInterface)[]>['encrypt', 'decrypt', 'generateDataKey']).reduce(wrapOperation, client)
119117

120118
/* Wrap each of the operations to cache the client on response */
121-
function wrapOperation (client: Client, name: KMSOperations): Client {
122-
// type params = Parameters<KMS[typeof name]>
123-
// type retValue = ReturnType<KMS[typeof name]>
119+
function wrapOperation (client: Client, name: keyof AwsEsdkKMSInterface): Client {
124120
const original = client[name]
125-
client[name] = function (...args: any): Promise<any> {
121+
client[name] = function wrappedOperation (this: Client, args: any): Promise<any> {
126122
// @ts-ignore (there should be a TypeScript solution for this)
127-
const v2vsV3Response = original.apply(client, args)
128-
const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response)
123+
const v2vsV3Response = original.call(client, args)
124+
const v2vsV3Promise = 'promise' in v2vsV3Response
125+
? v2vsV3Response.promise()
126+
: v2vsV3Response
129127
return v2vsV3Promise
130128
.then((response: any) => {
131129
clientsCache[region] = Object.assign(client, { encrypt, decrypt, generateDataKey })

modules/kms-keyring/src/kms_keyring.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
import { KmsClientSupplier } from './kms_client_supplier' // eslint-disable-line no-unused-vars
1717
import {
18-
KMS, // eslint-disable-line no-unused-vars
19-
RequiredDecryptOutput // eslint-disable-line no-unused-vars
18+
AwsEsdkKMSInterface, // eslint-disable-line no-unused-vars
19+
RequiredDecryptResponse // eslint-disable-line no-unused-vars
2020
} from './kms_types'
2121
import {
2222
needs,
@@ -34,15 +34,15 @@ import {
3434
import { KMS_PROVIDER_ID, generateDataKey, encrypt, decrypt, kmsResponseToEncryptedDataKey } from './helpers'
3535
import { regionFromKmsKeyArn } from './region_from_kms_key_arn'
3636

37-
export interface KmsKeyringInput<Client extends KMS> {
37+
export interface KmsKeyringInput<Client extends AwsEsdkKMSInterface> {
3838
clientProvider: KmsClientSupplier<Client>
3939
keyIds?: string[]
4040
generatorKeyId?: string
4141
grantTokens?: string[]
4242
discovery?: boolean
4343
}
4444

45-
export interface KeyRing<S extends SupportedAlgorithmSuites, Client extends KMS> extends Keyring<S> {
45+
export interface KeyRing<S extends SupportedAlgorithmSuites, Client extends AwsEsdkKMSInterface> extends Keyring<S> {
4646
keyIds: ReadonlyArray<string>
4747
generatorKeyId?: string
4848
clientProvider: KmsClientSupplier<Client>
@@ -52,15 +52,15 @@ export interface KeyRing<S extends SupportedAlgorithmSuites, Client extends KMS>
5252
_onDecrypt(material: DecryptionMaterial<S>, encryptedDataKeys: EncryptedDataKey[], context?: EncryptionContext): Promise<DecryptionMaterial<S>>
5353
}
5454

55-
export interface KmsKeyRingConstructible<S extends SupportedAlgorithmSuites, Client extends KMS> {
55+
export interface KmsKeyRingConstructible<S extends SupportedAlgorithmSuites, Client extends AwsEsdkKMSInterface> {
5656
new(input: KmsKeyringInput<Client>): KeyRing<S, Client>
5757
}
5858

5959
export interface KeyRingConstructible<S extends SupportedAlgorithmSuites> {
6060
new(): Keyring<S>
6161
}
6262

63-
export function KmsKeyringClass<S extends SupportedAlgorithmSuites, Client extends KMS> (
63+
export function KmsKeyringClass<S extends SupportedAlgorithmSuites, Client extends AwsEsdkKMSInterface> (
6464
BaseKeyring: KeyRingConstructible<S>
6565
): KmsKeyRingConstructible<S, Client> {
6666
class KmsKeyring extends BaseKeyring implements KeyRing<S, Client> {
@@ -160,7 +160,7 @@ export function KmsKeyringClass<S extends SupportedAlgorithmSuites, Client exten
160160
})
161161

162162
for (const edk of decryptableEDKs) {
163-
let dataKey: RequiredDecryptOutput|false = false
163+
let dataKey: RequiredDecryptResponse|false = false
164164
try {
165165
dataKey = await decrypt(clientProvider, edk, context, grantTokens)
166166
} catch (e) {

0 commit comments

Comments
 (0)