Skip to content

Commit f2f3c21

Browse files
authored
feat(kms): Adds support for hmac and sm2 key spec (#23866)
Fixes #23727 I have not added an integration test for SM2 because i can not deploy in the China Region. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Construct Runtime Dependencies: * [ ] This PR adds new construct runtime dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-construct-runtime-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f35b70b commit f2f3c21

16 files changed

+1065
-9
lines changed

packages/@aws-cdk/aws-kms/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,15 @@ const key = new kms.Key(this, 'MyKey', {
217217
runs the risk of the key becoming unmanageable if that user or role is deleted.
218218
It is highly recommended that the key policy grants access to the account root, rather than specific principals.
219219
See https://docs.aws.amazon.com/kms/latest/developerguide/key-policies.html for more information.
220+
221+
### HMAC specific key policies
222+
223+
HMAC keys have a different key policy than other KMS keys. They have a policy for generating and for verifying a MAC.
224+
The respective policies can be attached to a principal via the `grantGenerateMac` and `grantVerifyMac` methods.
225+
226+
```ts
227+
const key = new kms.Key(this, 'MyKey');
228+
const user = new iam.User(this, 'MyUser');
229+
key.grantGenerateMac(user); // Adds 'kms:GenerateMac' to the principal's policy
230+
key.grantVerifyMac(user); // Adds 'kms:VerifyMac' to the principal's policy
231+
```

packages/@aws-cdk/aws-kms/lib/alias.ts

+10
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ abstract class AliasBase extends Resource implements IAlias {
9393
public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant {
9494
return this.aliasTargetKey.grantEncryptDecrypt(grantee);
9595
}
96+
97+
grantGenerateMac(grantee: iam.IGrantable): iam.Grant {
98+
return this.aliasTargetKey.grantGenerateMac(grantee);
99+
}
100+
101+
grantVerifyMac(grantee: iam.IGrantable): iam.Grant {
102+
return this.aliasTargetKey.grantVerifyMac(grantee);
103+
}
96104
}
97105

98106
/**
@@ -160,6 +168,8 @@ export class Alias extends AliasBase {
160168
public grantDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
161169
public grantEncrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
162170
public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
171+
public grantGenerateMac(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
172+
public grantVerifyMac(grantee: iam.IGrantable): iam.Grant { return iam.Grant.drop(grantee, ''); }
163173
}
164174

165175
return new Import(scope, id);

packages/@aws-cdk/aws-kms/lib/key.ts

+101-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import * as iam from '@aws-cdk/aws-iam';
22
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
3-
import { FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, ResourceProps, Stack, Duration, Token, ContextProvider, Arn, ArnFormat } from '@aws-cdk/core';
3+
import {
4+
Arn,
5+
ArnFormat,
6+
ContextProvider,
7+
Duration,
8+
FeatureFlags,
9+
IResource,
10+
Lazy,
11+
RemovalPolicy,
12+
Resource,
13+
ResourceProps,
14+
Stack,
15+
Token,
16+
} from '@aws-cdk/core';
417
import * as cxapi from '@aws-cdk/cx-api';
518
import { Construct } from 'constructs';
619
import { Alias } from './alias';
@@ -60,6 +73,16 @@ export interface IKey extends IResource {
6073
* Grant encryption and decryption permissions using this key to the given principal
6174
*/
6275
grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant;
76+
77+
/**
78+
* Grant permissions to generating MACs to the given principal
79+
*/
80+
grantGenerateMac(grantee: iam.IGrantable): iam.Grant
81+
82+
/**
83+
* Grant permissions to verifying MACs to the given principal
84+
*/
85+
grantVerifyMac(grantee: iam.IGrantable): iam.Grant
6386
}
6487

6588
abstract class KeyBase extends Resource implements IKey {
@@ -193,6 +216,20 @@ abstract class KeyBase extends Resource implements IKey {
193216
return this.grant(grantee, ...[...perms.DECRYPT_ACTIONS, ...perms.ENCRYPT_ACTIONS]);
194217
}
195218

219+
/**
220+
* Grant permissions to generating MACs to the given principal
221+
*/
222+
public grantGenerateMac(grantee: iam.IGrantable): iam.Grant {
223+
return this.grant(grantee, ...perms.GENERATE_HMAC_ACTIONS);
224+
}
225+
226+
/**
227+
* Grant permissions to verifying MACs to the given principal
228+
*/
229+
public grantVerifyMac(grantee: iam.IGrantable): iam.Grant {
230+
return this.grant(grantee, ...perms.VERIFY_HMAC_ACTIONS);
231+
}
232+
196233
/**
197234
* Checks whether the grantee belongs to a stack that will be deployed
198235
* after the stack containing this key.
@@ -300,6 +337,41 @@ export enum KeySpec {
300337
* Valid usage: SIGN_VERIFY
301338
*/
302339
ECC_SECG_P256K1 = 'ECC_SECG_P256K1',
340+
341+
/**
342+
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA224.
343+
*
344+
* Valid usage: GENERATE_VERIFY_MAC
345+
*/
346+
HMAC_224 = 'HMAC_224',
347+
348+
/**
349+
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA256.
350+
*
351+
* Valid usage: GENERATE_VERIFY_MAC
352+
*/
353+
HMAC_256 = 'HMAC_256',
354+
355+
/**
356+
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA384.
357+
*
358+
* Valid usage: GENERATE_VERIFY_MAC
359+
*/
360+
HMAC_384 = 'HMAC_384',
361+
362+
/**
363+
* Hash-Based Message Authentication Code as defined in RFC 2104 using the message digest function SHA512.
364+
*
365+
* Valid usage: GENERATE_VERIFY_MAC
366+
*/
367+
HMAC_512 = 'HMAC_512',
368+
369+
/**
370+
* Elliptic curve key spec available only in China Regions.
371+
*
372+
* Valid usage: ENCRYPT_DECRYPT and SIGN_VERIFY
373+
*/
374+
SM2 = 'SM2',
303375
}
304376

305377
/**
@@ -315,6 +387,11 @@ export enum KeyUsage {
315387
* Signing and verification
316388
*/
317389
SIGN_VERIFY = 'SIGN_VERIFY',
390+
391+
/**
392+
* Generating and verifying MACs
393+
*/
394+
GENERATE_VERIFY_MAC = 'GENERATE_VERIFY_MAC',
318395
}
319396

320397
/**
@@ -595,9 +672,28 @@ export class Key extends KeyBase {
595672
KeySpec.ECC_NIST_P384,
596673
KeySpec.ECC_NIST_P521,
597674
KeySpec.ECC_SECG_P256K1,
675+
KeySpec.HMAC_224,
676+
KeySpec.HMAC_256,
677+
KeySpec.HMAC_384,
678+
KeySpec.HMAC_512,
598679
],
599680
[KeyUsage.SIGN_VERIFY]: [
600681
KeySpec.SYMMETRIC_DEFAULT,
682+
KeySpec.HMAC_224,
683+
KeySpec.HMAC_256,
684+
KeySpec.HMAC_384,
685+
KeySpec.HMAC_512,
686+
],
687+
[KeyUsage.GENERATE_VERIFY_MAC]: [
688+
KeySpec.RSA_2048,
689+
KeySpec.RSA_3072,
690+
KeySpec.RSA_4096,
691+
KeySpec.ECC_NIST_P256,
692+
KeySpec.ECC_NIST_P384,
693+
KeySpec.ECC_NIST_P521,
694+
KeySpec.ECC_SECG_P256K1,
695+
KeySpec.SYMMETRIC_DEFAULT,
696+
KeySpec.SM2,
601697
],
602698
};
603699
const keySpec = props.keySpec ?? KeySpec.SYMMETRIC_DEFAULT;
@@ -606,6 +702,10 @@ export class Key extends KeyBase {
606702
throw new Error(`key spec '${keySpec}' is not valid with usage '${keyUsage}'`);
607703
}
608704

705+
if (keySpec.startsWith('HMAC') && props.enableKeyRotation) {
706+
throw new Error('key rotation cannot be enabled on HMAC keys');
707+
}
708+
609709
if (keySpec !== KeySpec.SYMMETRIC_DEFAULT && props.enableKeyRotation) {
610710
throw new Error('key rotation cannot be enabled on asymmetric keys');
611711
}

packages/@aws-cdk/aws-kms/lib/private/perms.ts

+8
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ export const ENCRYPT_ACTIONS = [
2626
export const DECRYPT_ACTIONS = [
2727
'kms:Decrypt',
2828
];
29+
30+
export const GENERATE_HMAC_ACTIONS = [
31+
'kms:GenerateMac',
32+
];
33+
34+
export const VERIFY_HMAC_ACTIONS = [
35+
'kms:VerifyMac',
36+
];

packages/@aws-cdk/aws-kms/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"@aws-cdk/assertions": "0.0.0",
8484
"@aws-cdk/cdk-build-tools": "0.0.0",
8585
"@aws-cdk/integ-runner": "0.0.0",
86+
"@aws-cdk/integ-tests": "0.0.0",
8687
"@aws-cdk/cfn2ts": "0.0.0",
8788
"@aws-cdk/cloud-assembly-schema": "0.0.0",
8889
"@aws-cdk/pkglint": "0.0.0",

packages/@aws-cdk/aws-kms/test/alias.test.ts

+48
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Template } from '@aws-cdk/assertions';
22
import { ArnPrincipal, PolicyStatement } from '@aws-cdk/aws-iam';
3+
import * as iam from '@aws-cdk/aws-iam';
34
import { App, CfnOutput, Stack } from '@aws-cdk/core';
45
import { Construct } from 'constructs';
56
import { Alias } from '../lib/alias';
@@ -215,3 +216,50 @@ test('fails if alias policy is invalid', () => {
215216

216217
expect(() => app.synth()).toThrow(/A PolicyStatement must specify at least one \'action\' or \'notAction\'/);
217218
});
219+
220+
test('grants generate mac to the alias target key', () => {
221+
const app = new App();
222+
const stack = new Stack(app, 'my-stack');
223+
const key = new Key(stack, 'Key');
224+
const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' });
225+
const user = new iam.User(stack, 'User');
226+
227+
alias.grantGenerateMac(user);
228+
229+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
230+
PolicyDocument: {
231+
Statement: [
232+
{
233+
Action: 'kms:GenerateMac',
234+
Effect: 'Allow',
235+
Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] },
236+
},
237+
],
238+
Version: '2012-10-17',
239+
},
240+
});
241+
});
242+
243+
test('grants generate mac to the alias target key', () => {
244+
const app = new App();
245+
const stack = new Stack(app, 'my-stack');
246+
const key = new Key(stack, 'Key');
247+
const alias = new Alias(stack, 'Alias', { targetKey: key, aliasName: 'alias/foo' });
248+
const user = new iam.User(stack, 'User');
249+
250+
alias.grantVerifyMac(user);
251+
252+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
253+
PolicyDocument: {
254+
Statement: [
255+
{
256+
Action: 'kms:VerifyMac',
257+
Effect: 'Allow',
258+
Resource: { 'Fn::GetAtt': ['Key961B73FD', 'Arn'] },
259+
},
260+
],
261+
Version: '2012-10-17',
262+
},
263+
});
264+
});
265+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "29.0.0",
3+
"files": {
4+
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
5+
"source": {
6+
"path": "HmacIntegTestDefaultTestDeployAssert10C19CC4.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"Parameters": {
3+
"BootstrapVersion": {
4+
"Type": "AWS::SSM::Parameter::Value<String>",
5+
"Default": "/cdk-bootstrap/hnb659fds/version",
6+
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
7+
}
8+
},
9+
"Rules": {
10+
"CheckBootstrapVersion": {
11+
"Assertions": [
12+
{
13+
"Assert": {
14+
"Fn::Not": [
15+
{
16+
"Fn::Contains": [
17+
[
18+
"1",
19+
"2",
20+
"3",
21+
"4",
22+
"5"
23+
],
24+
{
25+
"Ref": "BootstrapVersion"
26+
}
27+
]
28+
}
29+
]
30+
},
31+
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
32+
}
33+
]
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "29.0.0",
3+
"files": {
4+
"9842a0bf1dbc91e927911dc4706da97c827206f07263b9d3ba005b2e5cebfc61": {
5+
"source": {
6+
"path": "aws-cdk-kms-hmac.template.json",
7+
"packaging": "file"
8+
},
9+
"destinations": {
10+
"current_account-current_region": {
11+
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12+
"objectKey": "9842a0bf1dbc91e927911dc4706da97c827206f07263b9d3ba005b2e5cebfc61.json",
13+
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
14+
}
15+
}
16+
}
17+
},
18+
"dockerImages": {}
19+
}

0 commit comments

Comments
 (0)