diff --git a/modules/kms-keyring-node/src/kms_keyring_node.ts b/modules/kms-keyring-node/src/kms_keyring_node.ts index 085caf910..8db5e097c 100644 --- a/modules/kms-keyring-node/src/kms_keyring_node.ts +++ b/modules/kms-keyring-node/src/kms_keyring_node.ts @@ -49,4 +49,4 @@ export class KmsKeyringNode extends KmsKeyringClass(KeyringNode as KeyRingConstr } immutableClass(KmsKeyringNode) -export { getKmsClient, cacheKmsClients, limitRegions, excludeRegions, cacheClients } +export { getKmsClient, cacheKmsClients, getClient, limitRegions, excludeRegions, cacheClients, KMS } diff --git a/modules/kms-keyring/package.json b/modules/kms-keyring/package.json index 8564f82c6..85248c221 100644 --- a/modules/kms-keyring/package.json +++ b/modules/kms-keyring/package.json @@ -17,19 +17,15 @@ "license": "Apache-2.0", "dependencies": { "@aws-crypto/material-management": "^0.2.0-preview.1", - "@aws-sdk/types": "0.1.0-preview.1", "tslib": "^1.9.3" }, "devDependencies": { - "@aws-sdk/client-kms-browser": "^0.1.0-preview.2", - "@aws-sdk/client-kms-node": "^0.1.0-preview.2", "@types/chai": "^4.1.4", "@types/chai-as-promised": "^7.1.0", "@types/mocha": "^5.2.5", "@types/node": "^11.11.4", "@typescript-eslint/eslint-plugin": "^1.9.0", "@typescript-eslint/parser": "^1.9.0", - "aws-sdk": "^2.443.0", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "mocha": "^5.2.0", diff --git a/modules/kms-keyring/src/helpers.ts b/modules/kms-keyring/src/helpers.ts index 69c48e384..0336ec3c5 100644 --- a/modules/kms-keyring/src/helpers.ts +++ b/modules/kms-keyring/src/helpers.ts @@ -15,16 +15,13 @@ import { KmsClientSupplier } from './kms_client_supplier' // eslint-disable-line no-unused-vars import { - KMS, // eslint-disable-line no-unused-vars - GenerateDataKeyInput, // eslint-disable-line no-unused-vars - EncryptInput, // eslint-disable-line no-unused-vars - DecryptInput, // eslint-disable-line no-unused-vars - GenerateDataKeyOutput, // eslint-disable-line no-unused-vars - RequiredGenerateDataKeyOutput, // eslint-disable-line no-unused-vars - EncryptOutput, // eslint-disable-line no-unused-vars - RequiredEncryptOutput, // eslint-disable-line no-unused-vars - DecryptOutput, // eslint-disable-line no-unused-vars - RequiredDecryptOutput // eslint-disable-line no-unused-vars + AwsEsdkKMSInterface, // eslint-disable-line no-unused-vars + GenerateDataKeyResponse, // eslint-disable-line no-unused-vars + RequiredGenerateDataKeyResponse, // eslint-disable-line no-unused-vars + EncryptResponse, // eslint-disable-line no-unused-vars + RequiredEncryptResponse, // eslint-disable-line no-unused-vars + DecryptResponse, // eslint-disable-line no-unused-vars + RequiredDecryptResponse // eslint-disable-line no-unused-vars } from './kms_types' import { regionFromKmsKeyArn } from './region_from_kms_key_arn' import { @@ -35,55 +32,55 @@ import { export const KMS_PROVIDER_ID = 'aws-kms' -export async function generateDataKey ( +export async function generateDataKey ( clientProvider: KmsClientSupplier, NumberOfBytes: number, KeyId: string, EncryptionContext?: EncryptionContext, GrantTokens?: string[] -): Promise { +): Promise { const region = regionFromKmsKeyArn(KeyId) const client = clientProvider(region) /* Check for early return (Postcondition): Client region was not provided. */ if (!client) return false - const request: GenerateDataKeyInput = { KeyId, GrantTokens, NumberOfBytes, EncryptionContext } as GenerateDataKeyInput - // @ts-ignore - const v2vsV3Response = client.generateDataKey(request) - const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response) - const dataKey: GenerateDataKeyOutput = await v2vsV3Promise + const v2vsV3Response = client.generateDataKey({ KeyId, GrantTokens, NumberOfBytes, EncryptionContext }) + const v2vsV3Promise = 'promise' in v2vsV3Response + ? v2vsV3Response.promise() + : v2vsV3Response + const dataKey = await v2vsV3Promise return safeGenerateDataKey(dataKey) } -export async function encrypt ( +export async function encrypt ( clientProvider: KmsClientSupplier, Plaintext: Uint8Array, KeyId: string, EncryptionContext?: EncryptionContext, GrantTokens?: string[] -): Promise { +): Promise { const region = regionFromKmsKeyArn(KeyId) const client = clientProvider(region) /* Check for early return (Postcondition): Client region was not provided. */ if (!client) return false - const request: EncryptInput = { KeyId, Plaintext, EncryptionContext, GrantTokens } as EncryptInput - // @ts-ignore - const v2vsV3Response = client.encrypt(request) - const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response) - const kmsEDK: EncryptOutput = await v2vsV3Promise + const v2vsV3Response = client.encrypt({ KeyId, Plaintext, EncryptionContext, GrantTokens }) + const v2vsV3Promise = 'promise' in v2vsV3Response + ? v2vsV3Response.promise() + : v2vsV3Response + const kmsEDK = await v2vsV3Promise return safeEncryptOutput(kmsEDK) } -export async function decrypt ( +export async function decrypt ( clientProvider: KmsClientSupplier, { providerId, providerInfo, encryptedDataKey }: EncryptedDataKey, EncryptionContext?: EncryptionContext, GrantTokens?: string[] -): Promise { +): Promise { /* Precondition: The EDK must be a KMS edk. */ needs(providerId === KMS_PROVIDER_ID, 'Unsupported providerId') const region = regionFromKmsKeyArn(providerInfo) @@ -91,14 +88,11 @@ export async function decrypt ( /* Check for early return (Postcondition): Client region was not provided. */ if (!client) return false - const request: DecryptInput = { - CiphertextBlob: encryptedDataKey, - EncryptionContext, - GrantTokens } as DecryptInput - // @ts-ignore - const v2vsV3Response = client.decrypt(request) - const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response) - const dataKey: DecryptOutput = await v2vsV3Promise + const v2vsV3Response = client.decrypt({ CiphertextBlob: encryptedDataKey, EncryptionContext, GrantTokens }) + const v2vsV3Promise = 'promise' in v2vsV3Response + ? v2vsV3Response.promise() + : v2vsV3Response + const dataKey = await v2vsV3Promise return safeDecryptOutput(dataKey) } @@ -106,42 +100,42 @@ export async function decrypt ( export function kmsResponseToEncryptedDataKey ({ KeyId: providerInfo, CiphertextBlob: encryptedDataKey -}: RequiredEncryptOutput) { +}: RequiredEncryptResponse) { return new EncryptedDataKey({ providerId: KMS_PROVIDER_ID, providerInfo, encryptedDataKey }) } -function safeGenerateDataKey ( - dataKey: GenerateDataKeyOutput -): RequiredGenerateDataKeyOutput { +function safeGenerateDataKey ( + dataKey: GenerateDataKeyResponse +): RequiredGenerateDataKeyResponse { /* Postcondition: KMS must return serializable generate data key. */ needs(typeof dataKey.KeyId === 'string' && dataKey.Plaintext instanceof Uint8Array && dataKey.CiphertextBlob instanceof Uint8Array, 'Malformed KMS response.') - return safePlaintext(dataKey) + return safePlaintext(dataKey) } -function safeEncryptOutput ( - dataKey: EncryptOutput -): RequiredEncryptOutput { +function safeEncryptOutput ( + dataKey: EncryptResponse +): RequiredEncryptResponse { /* Postcondition: KMS must return serializable encrypted data key. */ needs(typeof dataKey.KeyId === 'string' && dataKey.CiphertextBlob instanceof Uint8Array, 'Malformed KMS response.') - return dataKey + return dataKey } -function safeDecryptOutput ( - dataKey: DecryptOutput -): RequiredDecryptOutput { +function safeDecryptOutput ( + dataKey: DecryptResponse +): RequiredDecryptResponse { /* Postcondition: KMS must return usable decrypted key. */ needs(typeof dataKey.KeyId === 'string' && dataKey.Plaintext instanceof Uint8Array, 'Malformed KMS response.') - return safePlaintext(dataKey) + return safePlaintext(dataKey) } -function safePlaintext (dataKey: RequiredDecryptOutput | RequiredGenerateDataKeyOutput): RequiredDecryptOutput | RequiredGenerateDataKeyOutput { +function safePlaintext (dataKey: RequiredDecryptResponse | RequiredGenerateDataKeyResponse): RequiredDecryptResponse | RequiredGenerateDataKeyResponse { /* The KMS Client *may* return a Buffer that is not isolated. * i.e. the byteOffset !== 0. * This means that the unencrypted data key is possibly accessible to someone else. diff --git a/modules/kms-keyring/src/kms_client_supplier.ts b/modules/kms-keyring/src/kms_client_supplier.ts index 9f7a8cdc9..5f2370773 100644 --- a/modules/kms-keyring/src/kms_client_supplier.ts +++ b/modules/kms-keyring/src/kms_client_supplier.ts @@ -15,27 +15,25 @@ import { needs } from '@aws-crypto/material-management' import { - KMS, // eslint-disable-line no-unused-vars - KMSConfiguration, // eslint-disable-line no-unused-vars - KMSOperations // eslint-disable-line no-unused-vars + AwsEsdkKMSInterface // eslint-disable-line no-unused-vars } from './kms_types' -interface KMSConstructibleNonOption { +interface KMSConstructibleNonOption { new(config: Config) : Client } -interface KMSConstructibleOption { +interface KMSConstructibleOption { new(config?: Config) : Client } -export type KMSConstructible = KMSConstructibleNonOption | KMSConstructibleOption +export type KMSConstructible = KMSConstructibleNonOption | KMSConstructibleOption -export interface KmsClientSupplier { +export interface KmsClientSupplier { /* KmsClientProvider is allowed to return undefined if, for example, user wants to exclude particular regions. */ (region: string): Client | false } -export function getClient ( +export function getClient ( KMSClient: KMSConstructible, defaultConfig?: Config ): KmsClientSupplier { @@ -58,7 +56,7 @@ export function getClient ( } } -export function limitRegions ( +export function limitRegions ( regions: string[], getClient: KmsClientSupplier ): KmsClientSupplier { @@ -71,7 +69,7 @@ export function limitRegions ( } } -export function excludeRegions ( +export function excludeRegions ( regions: string[], getClient: KmsClientSupplier ): KmsClientSupplier { @@ -84,7 +82,7 @@ export function excludeRegions ( } } -export function cacheClients ( +export function cacheClients ( getClient: KmsClientSupplier ): KmsClientSupplier { const clientsCache: {[key: string]: Client|false} = {} @@ -103,7 +101,7 @@ export function cacheClients ( * This does *not* mean that this call is successful, * only that the region is backed by a functional KMS service. */ -function deferCache ( +function deferCache ( clientsCache: {[key: string]: Client|false}, region: string, client: Client|false @@ -115,17 +113,17 @@ function deferCache ( } const { encrypt, decrypt, generateDataKey } = client - return (['encrypt', 'decrypt', 'generateDataKey']).reduce(wrapOperation, client) + return (<(keyof AwsEsdkKMSInterface)[]>['encrypt', 'decrypt', 'generateDataKey']).reduce(wrapOperation, client) /* Wrap each of the operations to cache the client on response */ - function wrapOperation (client: Client, name: KMSOperations): Client { - // type params = Parameters - // type retValue = ReturnType + function wrapOperation (client: Client, name: keyof AwsEsdkKMSInterface): Client { const original = client[name] - client[name] = function (...args: any): Promise { + client[name] = function wrappedOperation (this: Client, args: any): Promise { // @ts-ignore (there should be a TypeScript solution for this) - const v2vsV3Response = original.apply(client, args) - const v2vsV3Promise = (v2vsV3Response.promise ? v2vsV3Response.promise() : v2vsV3Response) + const v2vsV3Response = original.call(client, args) + const v2vsV3Promise = 'promise' in v2vsV3Response + ? v2vsV3Response.promise() + : v2vsV3Response return v2vsV3Promise .then((response: any) => { clientsCache[region] = Object.assign(client, { encrypt, decrypt, generateDataKey }) diff --git a/modules/kms-keyring/src/kms_keyring.ts b/modules/kms-keyring/src/kms_keyring.ts index 4795805ae..8b07edc2d 100644 --- a/modules/kms-keyring/src/kms_keyring.ts +++ b/modules/kms-keyring/src/kms_keyring.ts @@ -15,8 +15,8 @@ import { KmsClientSupplier } from './kms_client_supplier' // eslint-disable-line no-unused-vars import { - KMS, // eslint-disable-line no-unused-vars - RequiredDecryptOutput // eslint-disable-line no-unused-vars + AwsEsdkKMSInterface, // eslint-disable-line no-unused-vars + RequiredDecryptResponse // eslint-disable-line no-unused-vars } from './kms_types' import { needs, @@ -34,7 +34,7 @@ import { import { KMS_PROVIDER_ID, generateDataKey, encrypt, decrypt, kmsResponseToEncryptedDataKey } from './helpers' import { regionFromKmsKeyArn } from './region_from_kms_key_arn' -export interface KmsKeyringInput { +export interface KmsKeyringInput { clientProvider: KmsClientSupplier keyIds?: string[] generatorKeyId?: string @@ -42,7 +42,7 @@ export interface KmsKeyringInput { discovery?: boolean } -export interface KeyRing extends Keyring { +export interface KeyRing extends Keyring { keyIds: ReadonlyArray generatorKeyId?: string clientProvider: KmsClientSupplier @@ -52,7 +52,7 @@ export interface KeyRing _onDecrypt(material: DecryptionMaterial, encryptedDataKeys: EncryptedDataKey[], context?: EncryptionContext): Promise> } -export interface KmsKeyRingConstructible { +export interface KmsKeyRingConstructible { new(input: KmsKeyringInput): KeyRing } @@ -60,7 +60,7 @@ export interface KeyRingConstructible { new(): Keyring } -export function KmsKeyringClass ( +export function KmsKeyringClass ( BaseKeyring: KeyRingConstructible ): KmsKeyRingConstructible { class KmsKeyring extends BaseKeyring implements KeyRing { @@ -160,7 +160,7 @@ export function KmsKeyringClass = Client extends KMSv3Node - ? import('@aws-sdk/client-kms-node').GenerateDataKeyInput - : Client extends KMSv3Browser - ? import('@aws-sdk/client-kms-browser').GenerateDataKeyInput - : Client extends KMSv2 - ? import('aws-sdk').KMS.GenerateDataKeyRequest - : never - -export type EncryptInput = Client extends KMSv3Node - ? import('@aws-sdk/client-kms-node').EncryptInput - : Client extends KMSv3Browser - ? import('@aws-sdk/client-kms-browser').EncryptInput - : Client extends KMSv2 - ? import('aws-sdk').KMS.EncryptRequest - : never - -export type DecryptInput = Client extends KMSv3Node - ? import('@aws-sdk/client-kms-node').DecryptInput - : Client extends KMSv3Browser - ? import('@aws-sdk/client-kms-browser').DecryptInput - : Client extends KMSv2 - ? import('aws-sdk').KMS.DecryptRequest - : never +export interface DecryptRequest { + CiphertextBlob: Uint8Array + EncryptionContext?: EncryptionContext + GrantTokens?: string[] +} -export type GenerateDataKeyOutput = Client extends KMSv3Node - ? import('@aws-sdk/client-kms-node').GenerateDataKeyOutput - : Client extends KMSv3Browser - ? import('@aws-sdk/client-kms-browser').GenerateDataKeyOutput - : Client extends KMSv2 - ? import('aws-sdk').KMS.GenerateDataKeyResponse - : never +interface Blob {} +type Data = string | Buffer | Uint8Array | Blob +export interface DecryptResponse { + KeyId?: string + Plaintext?: Data +} -export interface RequiredGenerateDataKeyOutput { - CiphertextBlob: Uint8Array +export interface RequiredDecryptResponse extends Required{ Plaintext: Uint8Array - KeyId: string } -export type EncryptOutput = Client extends KMSv3Node - ? import('@aws-sdk/client-kms-node').EncryptOutput - : Client extends KMSv3Browser - ? import('@aws-sdk/client-kms-browser').EncryptOutput - : Client extends KMSv2 - ? import('aws-sdk').KMS.EncryptResponse - : never +export interface EncryptRequest { + KeyId: string + Plaintext: Uint8Array + EncryptionContext?: EncryptionContext + GrantTokens?: string[] +} +export interface EncryptResponse { + CiphertextBlob?: Data + KeyId?: string +} -export interface RequiredEncryptOutput { +export interface RequiredEncryptResponse extends Required{ CiphertextBlob: Uint8Array +} + +export interface GenerateDataKeyRequest { KeyId: string + EncryptionContext?: EncryptionContext + NumberOfBytes?: number + GrantTokens?: string[] } -export type DecryptOutput = Client extends KMSv3Node - ? import('@aws-sdk/client-kms-node').DecryptOutput - : Client extends KMSv3Browser - ? import('@aws-sdk/client-kms-browser').DecryptOutput - : Client extends KMSv2 - ? import('aws-sdk').KMS.DecryptResponse - : never +export interface GenerateDataKeyResponse { + CiphertextBlob?: Data + Plaintext?: Data + KeyId?: string +} -export interface RequiredDecryptOutput { - KeyId: string +export interface RequiredGenerateDataKeyResponse extends Required{ + CiphertextBlob: Uint8Array Plaintext: Uint8Array } -export type KMSOperations = keyof Pick +export interface AwsSdkV2Response { + promise(): Promise +} + +export interface AwsEsdkKMSInterface { + decrypt(args: DecryptRequest): Promise|AwsSdkV2Response + encrypt(args: EncryptRequest): Promise|AwsSdkV2Response + generateDataKey(args: GenerateDataKeyRequest): Promise|AwsSdkV2Response +}