From b07bd5298fde1cae24200ffdade77a8dfb95aa53 Mon Sep 17 00:00:00 2001 From: Shinya Tsuda Date: Sat, 1 May 2021 02:29:52 +0900 Subject: [PATCH 1/4] feat(kms): allow specifying key spec and key usage This allows specifying key spec and key usage, so you can create asymmetric keys. fixes #5639 --- packages/@aws-cdk/aws-kms/lib/key.ts | 112 ++++++++++++++++++ .../aws-kms/test/integ.key.expected.json | 37 ++++++ packages/@aws-cdk/aws-kms/test/integ.key.ts | 8 +- packages/@aws-cdk/aws-kms/test/key.test.ts | 43 +++++++ 4 files changed, 199 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 2176d28f5b5bc..6e49023eae7f7 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -247,6 +247,85 @@ abstract class KeyBase extends Resource implements IKey { } } +/** + * The key spec, represents the cryptographic configuration of keys. + */ +export enum KeySpec { + /** + * The default key spec. + * + * Valid usage: ENCRYPT_DECRYPT + */ + SYMMETRIC_DEFAULT = 'SYMMETRIC_DEFAULT', + + /** + * RSA with 2048 bits of key. + * + * Valid usage: ENCRYPT_DECRYPT and SIGN_VERIFY + */ + RSA_2048 = 'RSA_2048', + + /** + * RSA with 3072 bits of key. + * + * Valid usage: ENCRYPT_DECRYPT and SIGN_VERIFY + */ + RSA_3072 = 'RSA_3072', + + /** + * RSA with 4096 bits of key. + * + * Valid usage: ENCRYPT_DECRYPT and SIGN_VERIFY + */ + RSA_4096 = 'RSA_4096', + + /** + * NIST FIPS 186-4, Section 6.4, ECDSA signature using the curve specified by the key and + * SHA-256 for the message digest. + * + * Valid usage: SIGN_VERIFY + */ + ECC_NIST_P256 = 'ECC_NIST_P256', + + /** + * NIST FIPS 186-4, Section 6.4, ECDSA signature using the curve specified by the key and + * SHA-384 for the message digest. + * + * Valid usage: SIGN_VERIFY + */ + ECC_NIST_P384 = 'ECC_NIST_P384', + + /** + * NIST FIPS 186-4, Section 6.4, ECDSA signature using the curve specified by the key and + * SHA-512 for the message digest. + * + * Valid usage: SIGN_VERIFY + */ + ECC_NIST_P521 = 'ECC_NIST_P521', + + /** + * Standards for Efficient Cryptography 2, Section 2.4.1, ECDSA signature on the Koblitz curve. + * + * Valid usage: SIGN_VERIFY + */ + ECC_SECG_P256K1 = 'ECC_SECG_P256K1', +} + +/** + * The key usage, represents the cryptographic operations of keys. + */ +export enum KeyUsage { + /** + * Encryption and decryption. + */ + ENCRYPT_DECRYPT = 'ENCRYPT_DECRYPT', + + /** + * Signing and verification + */ + SIGN_VERIFY = 'SIGN_VERIFY', +} + /** * Construction properties for a KMS Key object */ @@ -282,6 +361,20 @@ export interface KeyProps { */ readonly enabled?: boolean; + /** + * The cryptographic configuration of the key. The valid value depends on usage of the key. + * + * @default - KeySpec.SYMMETRIC_DEFAULT + */ + readonly keySpec?: KeySpec; + + /** + * The cryptographic operations for which the key can be used. + * + * @default - KeyUsage.ENCRYPT_DECRYPT + */ + readonly keyUsage?: KeyUsage; + /** * Custom policy document to attach to the KMS key. * @@ -394,6 +487,23 @@ export class Key extends KeyBase { constructor(scope: Construct, id: string, props: KeyProps = {}) { super(scope, id); + const denyLists = { + [KeyUsage.ENCRYPT_DECRYPT]: [ + KeySpec.ECC_NIST_P256, + KeySpec.ECC_NIST_P384, + KeySpec.ECC_NIST_P521, + KeySpec.ECC_SECG_P256K1, + ], + [KeyUsage.SIGN_VERIFY]: [ + KeySpec.SYMMETRIC_DEFAULT, + ], + }; + const keySpec = props.keySpec ?? KeySpec.SYMMETRIC_DEFAULT; + const keyUsage = props.keyUsage ?? KeyUsage.ENCRYPT_DECRYPT; + if (denyLists[keyUsage].includes(keySpec)) { + throw new Error(`key spec '${keySpec}' is not valid with usage '${keyUsage}'`); + } + const defaultKeyPoliciesFeatureEnabled = FeatureFlags.of(this).isEnabled(cxapi.KMS_DEFAULT_KEY_POLICIES); this.policy = props.policy ?? new iam.PolicyDocument(); @@ -428,6 +538,8 @@ export class Key extends KeyBase { description: props.description, enableKeyRotation: props.enableKeyRotation, enabled: props.enabled, + keySpec: props.keySpec, + keyUsage: props.keyUsage, keyPolicy: this.policy, pendingWindowInDays: pendingWindowInDays, }); diff --git a/packages/@aws-cdk/aws-kms/test/integ.key.expected.json b/packages/@aws-cdk/aws-kms/test/integ.key.expected.json index 6ed1da4638a2f..761498395aa0e 100644 --- a/packages/@aws-cdk/aws-kms/test/integ.key.expected.json +++ b/packages/@aws-cdk/aws-kms/test/integ.key.expected.json @@ -56,6 +56,43 @@ ] } } + }, + "AsymmetricKey26BBC514": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "KeySpec": "ECC_NIST_P256", + "KeyUsage": "SIGN_VERIFY" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/test/integ.key.ts b/packages/@aws-cdk/aws-kms/test/integ.key.ts index a9c5c22d2a238..18a7da10d50a2 100644 --- a/packages/@aws-cdk/aws-kms/test/integ.key.ts +++ b/packages/@aws-cdk/aws-kms/test/integ.key.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; -import { Key } from '../lib'; +import { Key, KeySpec, KeyUsage } from '../lib'; const app = new App(); @@ -16,4 +16,10 @@ key.addToResourcePolicy(new iam.PolicyStatement({ key.addAlias('alias/bar'); +new Key(stack, 'AsymmetricKey', { + keySpec: KeySpec.ECC_NIST_P256, + keyUsage: KeyUsage.SIGN_VERIFY, + removalPolicy: RemovalPolicy.DESTROY, +}); + app.synth(); diff --git a/packages/@aws-cdk/aws-kms/test/key.test.ts b/packages/@aws-cdk/aws-kms/test/key.test.ts index 50c23fc265bb7..bd34e3935a583 100644 --- a/packages/@aws-cdk/aws-kms/test/key.test.ts +++ b/packages/@aws-cdk/aws-kms/test/key.test.ts @@ -844,3 +844,46 @@ describe('when the defaultKeyPolicies feature flag is disabled', () => { }); }); }); + +describe('key specs and key usages', () => { + testFutureBehavior('both usage and spec are specified', flags, cdk.App, (app) => { + const stack = new cdk.Stack(app); + new kms.Key(stack, 'Key', { keySpec: kms.KeySpec.ECC_SECG_P256K1, keyUsage: kms.KeyUsage.SIGN_VERIFY }); + + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeySpec: 'ECC_SECG_P256K1', + KeyUsage: 'SIGN_VERIFY', + }); + }); + + testFutureBehavior('only key usage is specified', flags, cdk.App, (app) => { + const stack = new cdk.Stack(app); + new kms.Key(stack, 'Key', { keyUsage: kms.KeyUsage.ENCRYPT_DECRYPT }); + + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeyUsage: 'ENCRYPT_DECRYPT', + }); + }); + + testFutureBehavior('only key spec is specified', flags, cdk.App, (app) => { + const stack = new cdk.Stack(app); + new kms.Key(stack, 'Key', { keySpec: kms.KeySpec.RSA_4096 }); + + expect(stack).toHaveResourceLike('AWS::KMS::Key', { + KeySpec: 'RSA_4096', + }); + }); + + testFutureBehavior('invalid combinations of key specs and key usages', flags, cdk.App, (app) => { + const stack = new cdk.Stack(app); + + expect(() => new kms.Key(stack, 'Key1', { keySpec: kms.KeySpec.ECC_NIST_P256 })) + .toThrow('key spec \'ECC_NIST_P256\' is not valid with usage \'ENCRYPT_DECRYPT\''); + expect(() => new kms.Key(stack, 'Key2', { keySpec: kms.KeySpec.ECC_SECG_P256K1, keyUsage: kms.KeyUsage.ENCRYPT_DECRYPT })) + .toThrow('key spec \'ECC_SECG_P256K1\' is not valid with usage \'ENCRYPT_DECRYPT\''); + expect(() => new kms.Key(stack, 'Key3', { keySpec: kms.KeySpec.SYMMETRIC_DEFAULT, keyUsage: kms.KeyUsage.SIGN_VERIFY })) + .toThrow('key spec \'SYMMETRIC_DEFAULT\' is not valid with usage \'SIGN_VERIFY\''); + expect(() => new kms.Key(stack, 'Key4', { keyUsage: kms.KeyUsage.SIGN_VERIFY })) + .toThrow('key spec \'SYMMETRIC_DEFAULT\' is not valid with usage \'SIGN_VERIFY\''); + }); +}); From 48650599f7fb7d3fe2d638fb33b05b2b958c37a8 Mon Sep 17 00:00:00 2001 From: Shinya Tsuda Date: Sat, 1 May 2021 05:15:35 +0900 Subject: [PATCH 2/4] added validation to ensure key rotation is not enabled on asymmetric keys --- packages/@aws-cdk/aws-kms/lib/key.ts | 4 ++++ packages/@aws-cdk/aws-kms/test/key.test.ts | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 6e49023eae7f7..48c54efb64e79 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -504,6 +504,10 @@ export class Key extends KeyBase { throw new Error(`key spec '${keySpec}' is not valid with usage '${keyUsage}'`); } + if (keySpec !== KeySpec.SYMMETRIC_DEFAULT && props.enableKeyRotation) { + throw new Error('key rotation cannot be enabled on asymmetric keys'); + } + const defaultKeyPoliciesFeatureEnabled = FeatureFlags.of(this).isEnabled(cxapi.KMS_DEFAULT_KEY_POLICIES); this.policy = props.policy ?? new iam.PolicyDocument(); diff --git a/packages/@aws-cdk/aws-kms/test/key.test.ts b/packages/@aws-cdk/aws-kms/test/key.test.ts index bd34e3935a583..0bdb6c755d7bb 100644 --- a/packages/@aws-cdk/aws-kms/test/key.test.ts +++ b/packages/@aws-cdk/aws-kms/test/key.test.ts @@ -886,4 +886,11 @@ describe('key specs and key usages', () => { expect(() => new kms.Key(stack, 'Key4', { keyUsage: kms.KeyUsage.SIGN_VERIFY })) .toThrow('key spec \'SYMMETRIC_DEFAULT\' is not valid with usage \'SIGN_VERIFY\''); }); + + testFutureBehavior('fails if key rotation enabled on asymmetric key', flags, cdk.App, (app) => { + const stack = new cdk.Stack(app); + + expect(() => new kms.Key(stack, 'Key', { enableKeyRotation: true, keySpec: kms.KeySpec.RSA_3072 })) + .toThrow('key rotation cannot be enabled on asymmetric keys'); + }); }); From 30f20af420a256366c7bfb87046559b76095b917 Mon Sep 17 00:00:00 2001 From: Shinya Tsuda Date: Sat, 8 May 2021 10:06:06 +0900 Subject: [PATCH 3/4] updated docstrings. --- packages/@aws-cdk/aws-kms/lib/key.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 48c54efb64e79..bea78532bbf90 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -364,14 +364,20 @@ export interface KeyProps { /** * The cryptographic configuration of the key. The valid value depends on usage of the key. * - * @default - KeySpec.SYMMETRIC_DEFAULT + * IMPORTANT: If you change this property of an existing key, the existing key is scheduled for deletion + * and a new key is created with the specified value. + * + * @default KeySpec.SYMMETRIC_DEFAULT */ readonly keySpec?: KeySpec; /** * The cryptographic operations for which the key can be used. * - * @default - KeyUsage.ENCRYPT_DECRYPT + * IMPORTANT: If you change this property of an existing key, the existing key is scheduled for deletion + * and a new key is created with the specified value. + * + * @default KeyUsage.ENCRYPT_DECRYPT */ readonly keyUsage?: KeyUsage; From 98231949897402007317c2b5ed89dce1514587e4 Mon Sep 17 00:00:00 2001 From: Shinya Tsuda Date: Sat, 8 May 2021 10:32:55 +0900 Subject: [PATCH 4/4] added example in the README. --- packages/@aws-cdk/aws-kms/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/@aws-cdk/aws-kms/README.md b/packages/@aws-cdk/aws-kms/README.md index 495f42a16ec25..a576eab5aab08 100644 --- a/packages/@aws-cdk/aws-kms/README.md +++ b/packages/@aws-cdk/aws-kms/README.md @@ -40,6 +40,18 @@ key.addAlias('alias/foo'); key.addAlias('alias/bar'); ``` + +Define a key with specific key spec and key usage: + +Valid `keySpec` values depends on `keyUsage` value. + +```ts +const key = new kms.Key(this, 'MyKey', { + keySpec: kms.KeySpec.ECC_SECG_P256K1, // Default to SYMMETRIC_DEFAULT + keyUsage: kms.KeyUsage.SIGN_VERIFY // and ENCRYPT_DECRYPT +}); +``` + ## Sharing keys between stacks To use a KMS key in a different stack in the same CDK application,