modules/example-node/hkr-demo/multi_tenant.demo.ts (129 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import {
KmsHierarchicalKeyRingNode,
BranchKeyStoreNode,
SrkCompatibilityKmsConfig,
EncryptionContext,
buildClient,
CommitmentPolicy,
KeyringNode,
BranchKeyIdSupplier,
} from '@aws-crypto/client-node'
import minimist from 'minimist'
import * as fs from 'fs'
import { exit } from 'process'
// read CLI args
const args = minimist(process.argv.slice(2))
// map A and B to respective branch IDs
const tenantMap: { [key: string]: string } = {
A: '38853b56-19c6-4345-9cb5-afc2a25dcdd1',
B: '2c583585-5770-467d-8f59-b346d0ed1994',
}
// preprocess CLI args and return them under an object with named fields
function getCliArgs() {
const operation = args.operation
if (!operation) {
throw new Error('Must specify operation to perform')
}
let inFile: string = args.inputFile
if (!inFile) {
throw new Error("Must specify input's file path")
}
inFile = inFile.replace('~', '/Users/nvobilis')
let outFile = args.outputFile
if (!outFile) {
throw new Error("Must specify output's file path")
}
outFile = outFile.replace('~', '/Users/nvobilis')
const tenant: string = args.tenant
if (!tenant) {
throw new Error("Must specify tenant's branch key ID for this operation")
}
return { operation, inFile, outFile, tenant }
}
// a dummy branch key id supplier which looks for a field with key "branchKeyId"
// inside the EC
class ExampleBranchKeyIdSupplier implements BranchKeyIdSupplier {
getBranchKeyId(encryptionContext: EncryptionContext): string {
return encryptionContext.branchKeyId
}
}
// configure the keystore
const branchKeyArn =
'arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126'
const keyStore = new BranchKeyStoreNode({
ddbTableName: 'KeyStoreDdbTable',
logicalKeyStoreName: 'KeyStoreDdbTable',
kmsConfiguration: new SrkCompatibilityKmsConfig(branchKeyArn),
})
// function to read input from a file
function readInputData(inFile: string) {
let inData = Buffer.alloc(0)
try {
inData = fs.readFileSync(inFile)
} catch (err) {
console.error(err)
exit(1)
}
return inData
}
// a function to write output to a file
function dumpOutputData(outFile: string, outData: Buffer) {
try {
fs.writeFileSync(outFile, outData)
} catch (err) {
console.error(err)
exit(1)
}
}
const { encrypt, decrypt } = buildClient(
CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)
// function to decrypt with the H-keyring
async function decryptEncryptedData(
encryptedData: Buffer,
keyring: KeyringNode
) {
const { plaintext: decryptedData, messageHeader } = await decrypt(
keyring,
encryptedData
)
const { encryptionContext } = messageHeader
Object.entries(encryptionContext).forEach(([key, value]) => {
if (encryptionContext[key] !== value) {
throw new Error('Encryption Context does not match expected values')
}
})
return decryptedData
}
// function to encrypt with the H-Keyring
async function encryptData(
data: Buffer,
keyring: KeyringNode,
encryptionContext: EncryptionContext
) {
const { result } = await encrypt(keyring, data, { encryptionContext })
return result
}
async function main() {
// read cli args
const { operation, inFile, outFile, tenant } = getCliArgs()
// based on CLI tenant arg, find the branch key id
const branchKeyId: string = tenantMap[tenant]
// read input from input file
const inData = readInputData(inFile)
let outData: Buffer = Buffer.alloc(0)
let msg: string
// if cli arg operation field is encrypt
if (operation === 'encrypt') {
// create a dynamic keyring and encrypt
const keyring = new KmsHierarchicalKeyRingNode({
branchKeyIdSupplier: new ExampleBranchKeyIdSupplier(),
keyStore,
cacheLimitTtl: 60,
})
const data = inData
outData = await encryptData(data, keyring, { branchKeyId })
msg = `Tenant ${tenant} has completed encryption`
} else {
// otherwise, create a static keyring and decrypt
const keyring = new KmsHierarchicalKeyRingNode({
branchKeyId,
keyStore,
cacheLimitTtl: 60,
})
const encryptedData = inData
try {
outData = await decryptEncryptedData(encryptedData, keyring)
} catch {
throw new Error(`Tenant ${tenant} cannot decrypt this encrypted message`)
}
msg = `Tenant ${tenant} has completed decryption`
}
// write output to output file
dumpOutputData(outFile, outData)
console.log(msg)
}
main()