in modules/example-node/src/kms-hierarchical-keyring/caching_cmm.ts [25:170]
export async function hKeyringCachingCMMNodeSimpleTest(
keyStoreTableName = 'KeyStoreDdbTable',
logicalKeyStoreName = keyStoreTableName,
kmsKeyId = 'arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126'
) {
// Configure your KeyStore resource.
// This SHOULD be the same configuration that you used
// to initially create and populate your KeyStore.
const keyStore = new BranchKeyStoreNode({
storage: {ddbTableName: keyStoreTableName},
logicalKeyStoreName: logicalKeyStoreName,
kmsConfiguration: { identifier: kmsKeyId },
})
// Here, you would call CreateKey to create an active branch keys
// However, the JS keystore does not currently support this operation, so we
// hard code the ID of an existing active branch key
const branchKeyId = '38853b56-19c6-4345-9cb5-afc2a25dcdd1'
// Create the Hierarchical Keyring.
const keyring = new KmsHierarchicalKeyRingNode({
branchKeyId,
keyStore,
cacheLimitTtl: 600, // 10 min
})
/* 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 }
}