Skip to content

Commit 36562a4

Browse files
committed
feat(hierarchical-keyring): add branch keystore (#620)
* chore: update package-lock.json (#1425) run `npm audit fix` * feat(branch-keystore): model AWS KMS configuration * feat(keystore): create class to model AWS KMS configuration for branch keystore * updated spec submodule to latest master * Update spec submodule to track master branch * feat(keystore): complete and test AWS KMS configuration class * chore: remove version file from branch-keystore-node module * chore: updated gitignore to ignore auto-generated version files in branch-keystore-node module * chore: removed changelog from branch-keystore-node module so that git can autogenerate it * added additional test for 100% coverage * fix(CI): bump up lerna from 7.3.0 to 8.1.6 (#615) * bump up lerna * Revert "bump up lerna" This reverts commit 6b3853ea7e184f485c30d45c50c18ba2d1c7e1d9. * Revert "feat(branch-keystore): model AWS KMS configuration" This reverts commit fa8eabcb46290fdd1dbc99baf8ee1a3d2facdc25. * Reapply "feat(branch-keystore): model AWS KMS configuration" This reverts commit 96e8b3085530a67fa46fab653e173eb1db01a7e9. * bump lerna up from 7.3.0 to 8.1.6 * add dependencies to ensure proper build * npm audit fix * fix test compliance issues * fix(branch-keystore): modify AWS KMS configuration to only support single region key compatibility for now (#608) * feat(branch-keystore): model AWS KMS configuration * feat(keystore): create class to model AWS KMS configuration for branch keystore * updated spec submodule to latest master * Update spec submodule to track master branch * feat(keystore): complete and test AWS KMS configuration class * chore: remove version file from branch-keystore-node module * chore: updated gitignore to ignore auto-generated version files in branch-keystore-node module * chore: removed changelog from branch-keystore-node module so that git can autogenerate it * added additional test for 100% coverage * made the fix and tested * remove duplicate compliance citations * specified compliance tests * fix compliance tests * fix duvet * remove duvet test annotations * add compliance tests for duvet * fix compliance tests for duvet * fix compliance tests for duvet * change lerna version * removed getParsedArn * separate kms config helpers from types * specified what's a 'bad arn' in tests * better error msg * no longer supressing errors from parseAwsKmsKeyArn * changed tests to assert for specific error messages * add a notice * sync lock file with package.json * consolidate helpers * compliance test citation * add additional flag methods to tell us config state * divide helper function tests and class method tests * add notice * Revert "change lerna version" This reverts commit a9ba112605c76295fb23cfda651f37eff9332e7b. * Update package-lock.json * Noop commit * wrote keystore * modify tests * modifying tests * add constructor tests * use material management module's branch key material class * more testing * create fixtures file to consolidate all test constants * rename * more tests and duvet * add copyright notice * fix test * fix test * change interface name * change param type to interface * change method signature * change return types because this is a node package * indicate integration tests * add mock network calls todo * better error message for getBranchKeyItem helper * more concise * leave grant tokens empty * modify mock todo * consolidate constants into one file * add notice * remove tests involving multi region keys * moved non-resource info out of fixtures * reinstall dependencies * sync lockfile after rebase * assume SRK * changes * rename keystore interface --------- Co-authored-by: seebees <[email protected]>
1 parent 0ebe6dc commit 36562a4

10 files changed

+1983
-0
lines changed

modules/branch-keystore-node/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
"license": "Apache-2.0",
2121
"dependencies": {
2222
"@aws-crypto/kms-keyring": "file:.../kms-keyring",
23+
"@aws-sdk/client-dynamodb": "^3.616.0",
24+
"@aws-sdk/util-dynamodb": "^3.616.0",
2325
"tslib": "^2.2.0"
2426
},
2527
"sideEffects": false,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { KmsConfig, RegionalKmsConfig } from './kms_config'
5+
import { KMSClient } from '@aws-sdk/client-kms'
6+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
7+
import {
8+
NodeBranchKeyMaterial,
9+
immutableClass,
10+
needs,
11+
readOnlyProperty,
12+
} from '@aws-crypto/material-management'
13+
import { v4 } from 'uuid'
14+
import {
15+
constructAuthenticatedEncryptionContext,
16+
constructBranchKeyMaterials,
17+
decryptBranchKey,
18+
getBranchKeyItem,
19+
validateBranchKeyRecord,
20+
} from './branch_keystore_helpers'
21+
import {
22+
BRANCH_KEY_ACTIVE_TYPE,
23+
BRANCH_KEY_TYPE_PREFIX,
24+
KMS_CLIENT_USER_AGENT,
25+
} from './constants'
26+
27+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#initialization
28+
//# The following inputs MAY be specified to create a KeyStore:
29+
30+
//# - [ID](#keystore-id)
31+
//# - [AWS KMS Grant Tokens](#aws-kms-grant-tokens)
32+
//# - [DynamoDb Client](#dynamodb-client)
33+
//# - [KMS Client](#kms-client)
34+
35+
//# The following inputs MUST be specified to create a KeyStore:
36+
37+
//# - [Table Name](#table-name)
38+
//# - [AWS KMS Configuration](#aws-kms-configuration)
39+
//# - [Logical KeyStore Name](#logical-keystore-name)
40+
export interface BranchKeyStoreNodeInput {
41+
ddbTableName: string
42+
logicalKeyStoreName: string
43+
kmsConfiguration: KmsConfig
44+
kmsClient?: KMSClient
45+
ddbClient?: DynamoDBClient
46+
keyStoreId?: string
47+
grantTokens?: string[]
48+
}
49+
50+
export interface IBranchKeyStoreNode {
51+
ddbTableName: string
52+
logicalKeyStoreName: string
53+
kmsConfiguration: Readonly<KmsConfig>
54+
kmsClient: KMSClient
55+
ddbClient: DynamoDBClient
56+
keyStoreId: string
57+
grantTokens: ReadonlyArray<string>
58+
59+
getActiveBranchKey(branchKeyId: string): Promise<NodeBranchKeyMaterial>
60+
getBranchKeyVersion(
61+
branchKeyId: string,
62+
branchKeyVersion: string
63+
): Promise<NodeBranchKeyMaterial>
64+
}
65+
66+
export class BranchKeyStoreNode implements IBranchKeyStoreNode {
67+
public declare ddbTableName: string
68+
public declare logicalKeyStoreName: string
69+
public declare kmsConfiguration: Readonly<KmsConfig>
70+
public declare kmsClient: KMSClient
71+
public declare ddbClient: DynamoDBClient
72+
public declare keyStoreId: string
73+
public declare grantTokens: ReadonlyArray<string>
74+
75+
constructor({
76+
ddbTableName,
77+
logicalKeyStoreName,
78+
kmsConfiguration,
79+
kmsClient,
80+
ddbClient,
81+
keyStoreId,
82+
grantTokens,
83+
}: BranchKeyStoreNodeInput) {
84+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#keystore-id
85+
//# The Identifier for this KeyStore.
86+
//# If one is not supplied, then a [version 4 UUID](https://www.ietf.org/rfc/rfc4122.txt) MUST be used.
87+
readOnlyProperty(this, 'keyStoreId', keyStoreId ? keyStoreId : v4())
88+
89+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#aws-kms-grant-tokens
90+
//# A list of AWS KMS [grant tokens](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#grant_token).
91+
readOnlyProperty(
92+
this,
93+
'grantTokens',
94+
Object.freeze(grantTokens ? grantTokens : [])
95+
)
96+
97+
needs(kmsConfiguration, 'AWS KMS Configuration required')
98+
readOnlyProperty(this, 'kmsConfiguration', Object.freeze(kmsConfiguration))
99+
100+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#dynamodb-client
101+
//# The DynamoDb Client used to put and get keys from the backing DDB table.
102+
103+
//# If the AWS KMS Configuration is KMS Key ARN or KMS MRKey ARN,
104+
//# and no DynamoDb Client is provided,
105+
//# a new DynamoDb Client MUST be created
106+
//# with the region of the supplied KMS ARN.
107+
108+
//# If the AWS KMS Configuration is Discovery,
109+
//# and no DynamoDb Client is provided,
110+
//# a new DynamoDb Client MUST be created
111+
//# with the default configuration.
112+
113+
//# If the AWS KMS Configuration is MRDiscovery,
114+
//# and no DynamoDb Client is provided,
115+
//# a new DynamoDb Client MUST be created
116+
//# with the region configured in the MRDiscovery.
117+
// TODO: when other KMS configuration types/classes are supported for the keystore,
118+
// verify the configuration object type to determine how we instantiate the
119+
// DDB client. This will ensure safe type casting.
120+
readOnlyProperty(
121+
this,
122+
'ddbClient',
123+
ddbClient ||
124+
new DynamoDBClient({
125+
region: (this.kmsConfiguration as RegionalKmsConfig).getRegion(),
126+
})
127+
)
128+
129+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#kms-client
130+
//# The KMS Client used when wrapping and unwrapping keys.
131+
132+
//# If the AWS KMS Configuration is KMS Key ARN or KMS MRKey ARN,
133+
//# and no KMS Client is provided,
134+
//# a new KMS Client MUST be created
135+
//# with the region of the supplied KMS ARN.
136+
137+
//# If the AWS KMS Configuration is Discovery,
138+
//# and no KMS Client is provided,
139+
//# a new KMS Client MUST be created
140+
//# with the default configuration.
141+
142+
//# If the AWS KMS Configuration is MRDiscovery,
143+
//# and no KMS Client is provided,
144+
//# a new KMS Client MUST be created
145+
//# with the region configured in the MRDiscovery.
146+
147+
//# On initialization the KeyStore SHOULD
148+
//# append a user agent string to the AWS KMS SDK Client with
149+
//# the value `aws-kms-hierarchy`.
150+
// TODO: when other KMS configuration types/classes are supported for the keystore,
151+
// verify the configuration object type to determine how we instantiate the
152+
// KMS client. This will ensure safe type casting.
153+
readOnlyProperty(
154+
this,
155+
'kmsClient',
156+
kmsClient ||
157+
new KMSClient({
158+
region: (this.kmsConfiguration as RegionalKmsConfig).getRegion(),
159+
customUserAgent: KMS_CLIENT_USER_AGENT,
160+
})
161+
)
162+
163+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#table-name
164+
//# The table name of the DynamoDb table that backs this Keystore.
165+
needs(ddbTableName, 'DynamoDb table name required')
166+
readOnlyProperty(this, 'ddbTableName', ddbTableName)
167+
168+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#logical-keystore-name
169+
//# This name is cryptographically bound to all data stored in this table,
170+
//# and logically separates data between different tables.
171+
172+
//# The logical keystore name MUST be bound to every created key.
173+
174+
//# There needs to be a one to one mapping between DynamoDB Table Names and the Logical KeyStore Name.
175+
//# This value can be set to the DynamoDB table name itself, but does not need to.
176+
177+
//# Controlling this value independently enables restoring from DDB table backups
178+
//# even when the table name after restoration is not exactly the same.
179+
needs(logicalKeyStoreName, 'Logical Keystore name required')
180+
readOnlyProperty(this, 'logicalKeyStoreName', logicalKeyStoreName)
181+
182+
// make this instance immutable
183+
Object.freeze(this)
184+
}
185+
186+
/**
187+
* This is a utility method that encapsulates the overlapping logic for getActiveBranchKey
188+
* and getBranchKeyVersion to retreive the branch key materials
189+
* @param branchKeyId
190+
* @param type - this could indicate an active or versioned request
191+
* @returns branch key materials
192+
*/
193+
private async _getBranchKeyMaterials(
194+
branchKeyId: string,
195+
type: string
196+
): Promise<NodeBranchKeyMaterial> {
197+
// get the ddb response item using the partition & sort keys
198+
const ddbBranchKeyItem = await getBranchKeyItem(this, branchKeyId, type)
199+
// validate and form the branch key record
200+
const ddbBranchKeyRecord = validateBranchKeyRecord(ddbBranchKeyItem)
201+
// construct an encryption context from the record
202+
const authenticatedEncryptionContext =
203+
constructAuthenticatedEncryptionContext(this, ddbBranchKeyRecord)
204+
// decrypt the encrypted branch key
205+
const branchKey = await decryptBranchKey(
206+
this,
207+
ddbBranchKeyRecord,
208+
authenticatedEncryptionContext
209+
)
210+
// construct branch key materials from the authenticated encryption context
211+
const branchKeyMaterials = constructBranchKeyMaterials(
212+
branchKey,
213+
branchKeyId,
214+
authenticatedEncryptionContext
215+
)
216+
return branchKeyMaterials
217+
}
218+
219+
async getActiveBranchKey(
220+
branchKeyId: string
221+
): Promise<NodeBranchKeyMaterial> {
222+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#getactivebranchkey
223+
//# On invocation, the caller:
224+
225+
//# - MUST supply a `branch-key-id`
226+
needs(branchKeyId, 'MUST supply a branch key id')
227+
228+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#getactivebranchkey
229+
//# To get the active version for the branch key id from the keystore
230+
//# this operation MUST call AWS DDB `GetItem`
231+
//# using the `branch-key-id` as the Partition Key and `"branch:ACTIVE"` value as the Sort Key.
232+
return await this._getBranchKeyMaterials(
233+
branchKeyId,
234+
BRANCH_KEY_ACTIVE_TYPE
235+
)
236+
}
237+
238+
async getBranchKeyVersion(
239+
branchKeyId: string,
240+
branchKeyVersion: string
241+
): Promise<NodeBranchKeyMaterial> {
242+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#getbranchkeyversion
243+
//# On invocation, the caller:
244+
245+
//# - MUST supply a `branch-key-id`
246+
//# - MUST supply a `branchKeyVersion`
247+
needs(
248+
branchKeyId && branchKeyVersion,
249+
'MUST supply a branch key id and branch key version'
250+
)
251+
252+
//= aws-encryption-sdk-specification/framework/branch-key-store.md#getbranchkeyversion
253+
//# To get a branch key from the keystore this operation MUST call AWS DDB `GetItem`
254+
//# using the `branch-key-id` as the Partition Key and "branch:version:" + `branchKeyVersion` value as the Sort Key.
255+
return await this._getBranchKeyMaterials(
256+
branchKeyId,
257+
BRANCH_KEY_TYPE_PREFIX + branchKeyVersion
258+
)
259+
}
260+
}
261+
262+
immutableClass(BranchKeyStoreNode)

0 commit comments

Comments
 (0)