in modules/example-node/src/kms-hierarchical-keyring/multi_tenancy.ts [91:217]
export async function hKeyringMultiTenancy(
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 two new active branch keys.
// However, the JS keystore does not currently support this operation, so we
// hard code the IDs of two existing active branch keys
const branchKeyIdA = '38853b56-19c6-4345-9cb5-afc2a25dcdd1'
const branchKeyIdB = '2c583585-5770-467d-8f59-b346d0ed1994'
// Create a branch key supplier that maps the branch key id to a more readable format
const branchKeyIdSupplier = new ExampleBranchKeyIdSupplier(
branchKeyIdA,
branchKeyIdB
)
// Create the Hierarchical Keyring.
const keyring = new KmsHierarchicalKeyRingNode({
branchKeyIdSupplier,
keyStore,
cacheLimitTtl: 600, // 10 min
})
// The Branch Key Id supplier uses the encryption context to determine which branch key id will
// be used to encrypt data.
// Create encryption context for TenantA
const encryptionContextAIn = {
tenant: 'TenantA',
encryption: 'context',
'is not': 'secret',
'but adds': 'useful metadata',
'that can help you': 'be confident that',
'the data you are handling': 'is what you think it is',
}
// Create encryption context for TenantB
const encryptionContextBIn = {
tenant: 'TenantB',
encryption: 'context',
'is not': 'secret',
'but adds': 'useful metadata',
'that can help you': 'be confident that',
'the data you are handling': 'is what you think it is',
}
/* Find data to encrypt. A simple string. */
const cleartext = 'asdf'
// Encrypt the data for encryptionContextA & encryptionContextB
const { result: encryptResultA } = await encrypt(keyring, cleartext, {
encryptionContext: encryptionContextAIn,
})
const { result: encryptResultB } = await encrypt(keyring, cleartext, {
encryptionContext: encryptionContextBIn,
})
// To attest that TenantKeyB cannot decrypt a message written by TenantKeyA
// let's construct more restrictive hierarchical keyrings.
const keyringA = new KmsHierarchicalKeyRingNode({
branchKeyId: branchKeyIdA,
keyStore,
cacheLimitTtl: 600,
})
const keyringB = new KmsHierarchicalKeyRingNode({
branchKeyId: branchKeyIdB,
keyStore,
cacheLimitTtl: 600,
})
let decryptAFailed = false
// Try to use keyring for Tenant B to decrypt a message encrypted with Tenant A's key
// Expected to fail.
try {
await decrypt(keyringB, encryptResultA)
} catch (e) {
decryptAFailed = true
}
let decryptBFailed = false
// Try to use keyring for Tenant A to decrypt a message encrypted with Tenant B's key
// Expected to fail.
try {
await decrypt(keyringA, encryptResultB)
} catch (e) {
decryptBFailed = true
}
// we will assert that both decrypts failed
const decryptsFailed = decryptAFailed && decryptBFailed
// Decrypt your encrypted data using the same keyring you used on encrypt.
const { plaintext: plaintextA, messageHeader: messageHeaderA } =
await decrypt(keyring, encryptResultA)
/* Grab the encryption context so you can verify it. */
const { encryptionContext: encryptionContextAOut } = messageHeaderA
Object.entries(encryptionContextAIn).forEach(([key, value]) => {
if (encryptionContextAOut[key] !== value)
throw new Error('Encryption Context does not match expected values')
})
const { plaintext: plaintextB, messageHeader: messageHeaderB } =
await decrypt(keyring, encryptResultB)
/* Grab the encryption context so you can verify it. */
const { encryptionContext: encryptionContextBOut } = messageHeaderB
Object.entries(encryptionContextBIn).forEach(([key, value]) => {
if (encryptionContextBOut[key] !== value)
throw new Error('Encryption Context does not match expected values')
})
// we will assert that both decrypted plaintexts are the same as the original
// cleartext
/* Return the values so the code can be tested. */
return { decryptsFailed, cleartext, plaintextA, plaintextB }
}