in modules/example-browser/src/caching_cmm.ts [44:225]
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 }
}