Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(kms): allow specifying key spec and key usage #14478

Merged
merged 6 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/@aws-cdk/aws-kms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
122 changes: 122 additions & 0 deletions packages/@aws-cdk/aws-kms/lib/key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -282,6 +361,26 @@ export interface KeyProps {
*/
readonly enabled?: boolean;

/**
* The cryptographic configuration of the key. The valid value depends on usage of the key.
njlynch marked this conversation as resolved.
Show resolved Hide resolved
*
* 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.
*
* 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;

/**
* Custom policy document to attach to the KMS key.
*
Expand Down Expand Up @@ -394,6 +493,27 @@ 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}'`);
}

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();
Expand Down Expand Up @@ -428,6 +548,8 @@ export class Key extends KeyBase {
description: props.description,
enableKeyRotation: props.enableKeyRotation,
enabled: props.enabled,
keySpec: props.keySpec,
keyUsage: props.keyUsage,
njlynch marked this conversation as resolved.
Show resolved Hide resolved
keyPolicy: this.policy,
pendingWindowInDays: pendingWindowInDays,
});
Expand Down
37 changes: 37 additions & 0 deletions packages/@aws-cdk/aws-kms/test/integ.key.expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
8 changes: 7 additions & 1 deletion packages/@aws-cdk/aws-kms/test/integ.key.ts
Original file line number Diff line number Diff line change
@@ -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();

Expand All @@ -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();
50 changes: 50 additions & 0 deletions packages/@aws-cdk/aws-kms/test/key.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,3 +844,53 @@ 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\'');
});

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');
});
});