From b1a0a90e77aaf94f6cb9ffdd20dddcb5346a2724 Mon Sep 17 00:00:00 2001 From: maz Date: Thu, 1 Aug 2024 07:47:38 +0900 Subject: [PATCH] feat: support KEY_AGREEMENT for keyUsage --- ...efaultTestDeployAssert01B3C2C1.assets.json | 19 +++ ...aultTestDeployAssert01B3C2C1.template.json | 36 ++++ .../aws-cdk-kms-key-agreement.assets.json | 19 +++ .../aws-cdk-kms-key-agreement.template.json | 75 +++++++++ .../cdk.out | 1 + .../integ.json | 12 ++ .../manifest.json | 113 +++++++++++++ .../tree.json | 154 ++++++++++++++++++ .../aws-kms/test/integ.key-key-agreement.ts | 19 +++ packages/aws-cdk-lib/aws-kms/lib/key.ts | 16 ++ packages/aws-cdk-lib/aws-kms/test/key.test.ts | 11 ++ 11 files changed, 475 insertions(+) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets.json new file mode 100644 index 0000000000000..6bbb0b68b0790 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.assets.json new file mode 100644 index 0000000000000..179c8cbc2f527 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "5e388ff4c487c120216268e3dfb8bf3b808fa9c7c4f04ec9d6a4c7932485d7af": { + "source": { + "path": "aws-cdk-kms-key-agreement.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "5e388ff4c487c120216268e3dfb8bf3b808fa9c7c4f04ec9d6a4c7932485d7af.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.template.json new file mode 100644 index 0000000000000..214496463dbac --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/aws-cdk-kms-key-agreement.template.json @@ -0,0 +1,75 @@ +{ + "Resources": { + "MyKeyAgreementKey1B696107": { + "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": "KEY_AGREEMENT" + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/cdk.out new file mode 100644 index 0000000000000..1f0068d32659a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/integ.json new file mode 100644 index 0000000000000..cc93f9792ecfd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "36.0.0", + "testCases": { + "KeyAgreementIntegTest/DefaultTest": { + "stacks": [ + "aws-cdk-kms-key-agreement" + ], + "assertionStack": "KeyAgreementIntegTest/DefaultTest/DeployAssert", + "assertionStackName": "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/manifest.json new file mode 100644 index 0000000000000..33651d9763463 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/manifest.json @@ -0,0 +1,113 @@ +{ + "version": "36.0.0", + "artifacts": { + "aws-cdk-kms-key-agreement.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-kms-key-agreement.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-kms-key-agreement": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-kms-key-agreement.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/5e388ff4c487c120216268e3dfb8bf3b808fa9c7c4f04ec9d6a4c7932485d7af.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-kms-key-agreement.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-kms-key-agreement.assets" + ], + "metadata": { + "/aws-cdk-kms-key-agreement/MyKeyAgreementKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyKeyAgreementKey1B696107" + } + ], + "/aws-cdk-kms-key-agreement/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-kms-key-agreement/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-kms-key-agreement" + }, + "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "KeyAgreementIntegTestDefaultTestDeployAssert01B3C2C1.assets" + ], + "metadata": { + "/KeyAgreementIntegTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/KeyAgreementIntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "KeyAgreementIntegTest/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/tree.json new file mode 100644 index 0000000000000..ce054ad8a3f8d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.js.snapshot/tree.json @@ -0,0 +1,154 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-cdk-kms-key-agreement": { + "id": "aws-cdk-kms-key-agreement", + "path": "aws-cdk-kms-key-agreement", + "children": { + "MyKeyAgreementKey": { + "id": "MyKeyAgreementKey", + "path": "aws-cdk-kms-key-agreement/MyKeyAgreementKey", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-kms-key-agreement/MyKeyAgreementKey/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Key", + "aws:cdk:cloudformation:props": { + "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": "KEY_AGREEMENT" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.CfnKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_kms.Key", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-cdk-kms-key-agreement/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-cdk-kms-key-agreement/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "KeyAgreementIntegTest": { + "id": "KeyAgreementIntegTest", + "path": "KeyAgreementIntegTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "KeyAgreementIntegTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "KeyAgreementIntegTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "KeyAgreementIntegTest/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "KeyAgreementIntegTest/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "KeyAgreementIntegTest/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.ts new file mode 100644 index 0000000000000..fb0fab0b967ca --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-kms/test/integ.key-key-agreement.ts @@ -0,0 +1,19 @@ +import { App, RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { Key, KeySpec, KeyUsage } from 'aws-cdk-lib/aws-kms'; + +const app = new App(); + +const stack = new Stack(app, 'aws-cdk-kms-key-agreement'); + +new Key(stack, 'MyKeyAgreementKey', { + removalPolicy: RemovalPolicy.DESTROY, + keyUsage: KeyUsage.KEY_AGREEMENT, + keySpec: KeySpec.ECC_NIST_P256, +}); + +new IntegTest(app, 'KeyAgreementIntegTest', { + testCases: [ + stack, + ], +}); diff --git a/packages/aws-cdk-lib/aws-kms/lib/key.ts b/packages/aws-cdk-lib/aws-kms/lib/key.ts index fec01eaade57d..c9258d8c861cb 100644 --- a/packages/aws-cdk-lib/aws-kms/lib/key.ts +++ b/packages/aws-cdk-lib/aws-kms/lib/key.ts @@ -404,6 +404,11 @@ export enum KeyUsage { * Generating and verifying MACs */ GENERATE_VERIFY_MAC = 'GENERATE_VERIFY_MAC', + + /** + * Deriving shared secrets + */ + KEY_AGREEMENT = 'KEY_AGREEMENT', } /** @@ -715,6 +720,17 @@ export class Key extends KeyBase { KeySpec.SYMMETRIC_DEFAULT, KeySpec.SM2, ], + [KeyUsage.KEY_AGREEMENT]: [ + KeySpec.SYMMETRIC_DEFAULT, + KeySpec.RSA_2048, + KeySpec.RSA_3072, + KeySpec.RSA_4096, + KeySpec.ECC_SECG_P256K1, + KeySpec.HMAC_224, + KeySpec.HMAC_256, + KeySpec.HMAC_384, + KeySpec.HMAC_512, + ], }; const keySpec = props.keySpec ?? KeySpec.SYMMETRIC_DEFAULT; const keyUsage = props.keyUsage ?? KeyUsage.ENCRYPT_DECRYPT; diff --git a/packages/aws-cdk-lib/aws-kms/test/key.test.ts b/packages/aws-cdk-lib/aws-kms/test/key.test.ts index 298a82730dc45..22f153d6b61b3 100644 --- a/packages/aws-cdk-lib/aws-kms/test/key.test.ts +++ b/packages/aws-cdk-lib/aws-kms/test/key.test.ts @@ -1321,6 +1321,17 @@ function generateInvalidKeySpecKeyUsageCombinations() { KeySpec.SYMMETRIC_DEFAULT, KeySpec.SM2, ], + [KeyUsage.KEY_AGREEMENT]: [ + KeySpec.SYMMETRIC_DEFAULT, + KeySpec.RSA_2048, + KeySpec.RSA_3072, + KeySpec.RSA_4096, + KeySpec.ECC_SECG_P256K1, + KeySpec.HMAC_224, + KeySpec.HMAC_256, + KeySpec.HMAC_384, + KeySpec.HMAC_512, + ], }; const testCases: { keySpec: KeySpec; keyUsage: KeyUsage; toString: () => string }[] = []; for (const keySpec in KeySpec) {