-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathcaching_cmm.ts
More file actions
225 lines (200 loc) · 8.99 KB
/
Copy pathcaching_cmm.ts
File metadata and controls
225 lines (200 loc) · 8.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
/* This is a simple example of using a caching CMM with a KMS keyring
* to encrypt and decrypt using the AWS Encryption SDK for Javascript in a browser.
*/
import {
KmsKeyringBrowser,
KMS,
getClient,
buildClient,
CommitmentPolicy,
WebCryptoCachingMaterialsManager,
getLocalCryptographicMaterialsCache,
} from '@aws-crypto/client-browser'
import { toBase64 } from '@aws-sdk/util-base64'
/* This builds the client with the REQUIRE_ENCRYPT_REQUIRE_DECRYPT commitment policy,
* which enforces that this client only encrypts using committing algorithm suites
* and enforces that this client
* will only decrypt encrypted messages
* that were created with a committing algorithm suite.
* This is the default commitment policy
* if you build the client with `buildClient()`.
*/
const { encrypt, decrypt } = buildClient(
CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)
/* This is injected by webpack.
* The webpack.DefinePlugin or @aws-sdk/karma-credential-loader will replace the values when bundling.
* The credential values are pulled from @aws-sdk/credential-provider-node
* Use any method you like to get credentials into the browser.
* See kms.webpack.config
*/
declare const credentials: {
accessKeyId: string
secretAccessKey: string
sessionToken: string
}
/* This is done to facilitate testing. */
export async function testCachingCMMExample() {
/* This example uses a KMS keyring. The generator key in a KMS keyring generates and encrypts the data key.
* The caller needs kms:GenerateDataKey permission on the CMK in generatorKeyId.
*/
const generatorKeyId =
'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'
/* Adding additional KMS keys that can decrypt.
* The caller must have kms:Encrypt permission for every CMK in keyIds.
* You might list several keys in different AWS Regions.
* This allows you to decrypt the data in any of the represented Regions.
* In this example, the generator key
* and the additional key are actually the same CMK.
* In `generatorId`, this CMK is identified by its alias ARN.
* In `keyIds`, this CMK is identified by its key ARN.
* In practice, you would specify different CMKs,
* or omit the `keyIds` parameter.
* This is *only* to demonstrate how the CMK ARNs are configured.
*/
const keyIds = [
'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f',
]
/* Need a client provider that will inject correct credentials.
* The credentials here are injected by webpack from your environment bundle is created
* The credential values are pulled using @aws-sdk/credential-provider-node.
* See kms.webpack.config
* You should inject your credential into the browser in a secure manner
* that works with your application.
*/
const { accessKeyId, secretAccessKey, sessionToken } = credentials
/* getClient takes a KMS client constructor
* and optional configuration values.
* The credentials can be injected here,
* because browsers do not have a standard credential discovery process the way Node.js does.
*/
const clientProvider = getClient(KMS, {
credentials: {
accessKeyId,
secretAccessKey,
sessionToken,
},
})
/* You must configure the KMS keyring with your KMS CMKs */
const keyring = new KmsKeyringBrowser({
clientProvider,
generatorKeyId,
keyIds,
})
/* Create a cache to hold the data keys (and related cryptographic material).
* This example uses the local cache provided by the Encryption SDK.
* The `capacity` value represents the maximum number of entries
* that the cache can hold.
* To make room for an additional entry,
* the cache evicts the oldest cached entry.
* Both encrypt and decrypt requests count independently towards this threshold.
* Entries that exceed any cache threshold are actively removed from the cache.
* By default, the SDK checks one item in the cache every 60 seconds (60,000 milliseconds).
* To change this frequency, pass in a `proactiveFrequency` value
* as the second parameter. This value is in milliseconds.
*/
const capacity = 100
const cache = getLocalCryptographicMaterialsCache(capacity)
/* The partition name lets multiple caching CMMs share the same local cryptographic cache.
* By default, the entries for each CMM are cached separately. However, if you want these CMMs to share the cache,
* use the same partition name for both caching CMMs.
* If you don't supply a partition name, the Encryption SDK generates a random name for each caching CMM.
* As a result, sharing elements in the cache MUST be an intentional operation.
*/
const partition = 'local partition name'
/* maxAge is the time in milliseconds that an entry will be cached.
* Elements are actively removed from the cache.
*/
const maxAge = 1000 * 60
/* The maximum number of bytes that will be encrypted under a single data key.
* This value is optional,
* but you should configure the lowest practical value.
*/
const maxBytesEncrypted = 100
/* The maximum number of messages that will be encrypted under a single data key.
* This value is optional,
* but you should configure the lowest practical value.
*/
const maxMessagesEncrypted = 10
const cachingCMM = new WebCryptoCachingMaterialsManager({
backingMaterials: keyring,
cache,
partition,
maxAge,
maxBytesEncrypted,
maxMessagesEncrypted,
})
/* Encryption context is a *very* powerful tool for controlling
* and managing access.
* When you pass an encryption context to the encrypt function,
* the encryption context is cryptographically bound to the ciphertext.
* If you don't pass in the same encryption context when decrypting,
* the decrypt function fails.
* The encryption context is ***not*** secret!
* Encrypted data is opaque.
* You can use an encryption context to assert things about the encrypted data.
* The encryption context helps you to determine
* whether the ciphertext you retrieved is the ciphertext you expect to decrypt.
* For example, if you are are only expecting data from 'us-west-2',
* the appearance of a different AWS Region in the encryption context can indicate malicious interference.
* See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context
*
* Also, cached data keys are reused ***only*** when the encryption contexts passed into the functions are an exact case-sensitive match.
* See: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/data-caching-details.html#caching-encryption-context
*/
const encryptionContext = {
stage: 'demo',
purpose: 'simple demonstration app',
origin: 'us-west-2',
}
/* Find data to encrypt. */
const plainText = new Uint8Array([1, 2, 3, 4, 5])
/* Encrypt the data.
* The caching CMM only reuses data keys
* when it know the length (or an estimate) of the plaintext.
* However, in the browser,
* you must provide all of the plaintext to the encrypt function.
* Therefore, the encrypt function in the browser knows the length of the plaintext
* and does not accept a plaintextLength option.
*/
const { result } = await encrypt(cachingCMM, plainText, { encryptionContext })
/* Log the plain text
* only for testing and to show that it works.
*/
console.log('plainText:', plainText)
document.write('</br>plainText:' + plainText + '</br>')
/* Log the base64-encoded result
* so that you can try decrypting it with another AWS Encryption SDK implementation.
*/
const resultBase64 = toBase64(result)
console.log(resultBase64)
document.write(resultBase64)
/* Decrypt the data.
* NOTE: This decrypt request will not use the data key
* that was cached during the encrypt operation.
* Data keys for encrypt and decrypt operations are cached separately.
*/
const { plaintext, messageHeader } = await decrypt(cachingCMM, result)
/* Grab the encryption context so you can verify it. */
const { encryptionContext: decryptedContext } = messageHeader
/* Verify the encryption context.
* If you use an algorithm suite with signing,
* the Encryption SDK adds a name-value pair to the encryption context that contains the public key.
* Because the encryption context might contain additional key-value pairs,
* do not include a test that requires that all key-value pairs match.
* Instead, verify that the key-value pairs that you supplied to the `encrypt` function are included in the encryption context that the `decrypt` function returns.
*/
Object.entries(encryptionContext).forEach(([key, value]) => {
if (decryptedContext[key] !== value)
throw new Error('Encryption Context does not match expected values')
})
/* Log the clear message
* only for testing and to show that it works.
*/
document.write('</br>Decrypted:' + plaintext)
console.log(plaintext)
/* Return the values to make testing easy. */
return { plainText, plaintext }
}