in modules/example-node/src/caching_cmm.ts [24:172]
export async function cachingCMMNodeSimpleTest() {
/* A KMS CMK is required to generate the data key.
* You need kms:GenerateDataKey permission on the CMK in generatorKeyId.
*/
const generatorKeyId =
'arn:aws:kms:us-west-2:658956600833:alias/EncryptDecrypt'
/* Adding alternate KMS keys that can decrypt.
* Access to kms:Encrypt is required 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',
]
/* The KMS keyring must be configured with the desired CMKs
* This example passes the keyring to the caching CMM
* instead of using it directly.
*/
const keyring = new KmsKeyringNode({ 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 amount of bytes that will be encrypted under a single data key.
* This value is optional,
* but you should configure the lowest value possible.
*/
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 value possible.
*/
const maxMessagesEncrypted = 10
const cachingCMM = new NodeCachingMaterialsManager({
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. A simple string. */
const cleartext = 'asdf'
/* Encrypt the data.
* The caching CMM only reuses data keys
* when it know the length (or an estimate) of the plaintext.
* If you do not know the length,
* because the data is a stream
* provide an estimate of the largest expected value.
*
* If your estimate is smaller than the actual plaintext length
* the AWS Encryption SDK will throw an exception.
*
* If the plaintext is not a stream,
* the AWS Encryption SDK uses the actual plaintext length
* instead of any length you provide.
*/
const { result } = await encrypt(cachingCMM, cleartext, {
encryptionContext,
plaintextLength: 4,
})
/* 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')
})
/* Return the values so the code can be tested. */
return { plaintext, result, cleartext, messageHeader }
}