export async function hKeyringMultiTenancy()

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 }
}