modules/example-node/hkr-demo/hkr.ts (102 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import {
buildClient,
CommitmentPolicy,
KeyringNode,
EncryptionContext,
} from '@aws-crypto/client-node'
import { randomBytes } from 'crypto'
import { KMSClient } from '@aws-sdk/client-kms'
import sinon from 'sinon'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
const { encrypt, decrypt } = buildClient(
CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)
const MAX_INPUT_LENGTH = 20
const MIN_INPUT_LENGTH = 15
const PURPLE_LOG = '\x1b[35m%s\x1b[0m'
const YELLOW_LOG = '\x1b[33m%s\x1b[0m'
const GREEN_LOG = '\x1b[32m%s\x1b[0m'
const RED_LOG = '\x1b[31m%s\x1b[0m'
// function to generate a random string
export function generateRandomString(minLength: number, maxLength: number) {
const randomLength =
Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength
return randomBytes(randomLength).toString('hex').slice(0, randomLength)
}
// function to encrypt, decrypt, and verify
export async function roundtrip(
keyring: KeyringNode,
context: EncryptionContext,
cleartext: string
) {
const { result } = await encrypt(keyring, cleartext, {
encryptionContext: context,
})
const { plaintext, messageHeader } = await decrypt(keyring, result)
const { encryptionContext } = messageHeader
Object.entries(context).forEach(([key, value]) => {
if (encryptionContext[key] !== value) {
throw new Error('Encryption Context does not match expected values')
}
})
return { plaintext, result, cleartext, messageHeader }
}
// run the roundtrips on the specified keyring
export async function runRoundTrips(
keyring: KeyringNode,
numRoundTrips: number
) {
// set up spies to monitor network call volume
const kmsSpy = sinon.spy(KMSClient.prototype, 'send')
const ddbSpy = sinon.spy(DynamoDBClient.prototype, 'send')
const padding = String(numRoundTrips).length
let successes = 0
console.log()
console.log(YELLO_LOG, `${keyring.constructor.name} Roundtrips`) // Print constructor name in yellow
console.time('Total runtime') // Start the timer
// for each roundtrip
for (let i = 0; i < numRoundTrips; i++) {
// create an encryption context
const encryptionContext = {
roundtrip: i.toString(),
}
// generate a random string
const encryptionInput = generateRandomString(
MIN_INPUT_LENGTH,
MAX_INPUT_LENGTH
)
// try to do the roundtrip. If any error arises, log it properly
let decryptionOutput: string
try {
const { plaintext } = await roundtrip(
keyring,
encryptionContext,
encryptionInput
)
decryptionOutput = plaintext.toString()
} catch {
decryptionOutput = 'ERROR'
}
const encryptionInputPadding = ' '.repeat(
MAX_INPUT_LENGTH - encryptionInput.length
)
const decryptionOutputPadding = ' '.repeat(
MAX_INPUT_LENGTH - decryptionOutput.length
)
// log message
const logMessage = `Roundtrip ${String(i + 1).padStart(
padding,
' '
)}: ${encryptionInput}${encryptionInputPadding} ----encrypt & decrypt----> ${decryptionOutput}${decryptionOutputPadding}`
// print the log green if successful. Otherwise, red
let logColor: string
if (encryptionInput === decryptionOutput) {
logColor = GREEN_LOG
successes += 1
} else {
logColor = RED_LOG
}
console.log(logColor, logMessage)
}
// print metrics for runtime and call volume
console.log()
console.log(YELLO_LOG, `${keyring.constructor.name} metrics`) // Print constructor name in yellow
console.timeEnd('Total runtime')
console.log(PURPLE_LOG, `KMS calls: ${kmsSpy.callCount}`)
console.log(PURPLE_LOG, `DynamoDB calls: ${ddbSpy.callCount}`)
console.log(
PURPLE_LOG,
`Successful roundtrips: ${successes} / ${numRoundTrips}`
)
kmsSpy.restore()
ddbSpy.restore()
}