Skip to content

Commit 8aa6bfa

Browse files
nvobilisseebees
authored andcommitted
demo code and readme
1 parent 69d177c commit 8aa6bfa

File tree

5 files changed

+515
-0
lines changed

5 files changed

+515
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# H-Keyring Intern Demo Guide
2+
3+
This guide provides detailed instructions on running the intern demos for the H-Keyring.
4+
5+
## Prerequisites
6+
7+
Before running the demos, navigate to the following directory:
8+
9+
```bash
10+
cd /private-aws-encryption-sdk-javascript-staging/modules/example-node/
11+
```
12+
13+
**Note:** All file paths in the CLI arguments must be absolute paths.
14+
15+
## Demo 1: Performance Comparison between KMS Keyring and H-Keyring
16+
17+
This demo compares the performance of the KMS Keyring and H-Keyring. In each roundtrip, a random string is generated, encrypted with a keyring, and then decrypted, expecting the original string to be returned.
18+
19+
The program logs roundtrip metrics for both the KMS Keyring and H-Keyring, including runtime, call volume, and success rate.
20+
21+
### Run the Demo
22+
23+
To run the performance comparison demo, use the following command:
24+
25+
```bash
26+
npx ts-node hkr-demo/hkr_vs_regular.demo.ts --numRoundTrips=<number of roundtrips>
27+
```
28+
29+
**Note:** The number of roundtrips defaults to 10 if not specified.
30+
31+
## Demo 2: Interoperability Test
32+
33+
This demo demonstrates the interoperability between the JS H-Keyring and other H-Keyrings.
34+
35+
### General Command
36+
37+
To encrypt a data file or decrypt an encrypted file using the JS H-Keyring, use the following command format:
38+
39+
```bash
40+
npx ts-node hkr-demo/interop.demo.ts <encrypt or decrypt> <input filepath> <output filepath>
41+
```
42+
43+
### Encrypting a Data File
44+
45+
To encrypt a data file with the JS H-Keyring, run:
46+
47+
```bash
48+
npx ts-node hkr-demo/interop.demo.ts encrypt <data filepath> <encrypted filepath>
49+
```
50+
51+
### Decrypting an Encrypted File
52+
53+
To decrypt an encrypted file with the JS H-Keyring, run:
54+
55+
```bash
56+
npx ts-node hkr-demo/interop.demo.ts decrypt <encrypted filepath> <decrypted filepath>
57+
```
58+
59+
## Demo 3: Multi-Tenancy
60+
61+
This demo showcases multi-tenant data isolation within a single keyring. You will observe failures when encrypting with tenant A and decrypting with tenant B (or vice versa). Tenant A and B are mapped to hard-coded branch IDs within the demo code in `./hkr-demo/multi_tenant.demo.ts`.
62+
63+
### General Command
64+
65+
To encrypt or decrypt with tenant A or B, use the following command format:
66+
67+
```bash
68+
npx ts-node hkr-demo/multi_tenant.demo.ts --operation=<encrypt or decrypt> --inputFile=<input filepath> --outputFile=<output filepath> --tenant=<A or B>
69+
```
70+
71+
### Encrypting with Tenant A
72+
73+
```bash
74+
npx ts-node hkr-demo/multi_tenant.demo.ts --operation=encrypt --inputFile=<data filepath> --outputFile=<encrypted filepath> --tenant=A
75+
```
76+
77+
### Decrypting with Tenant A
78+
79+
```bash
80+
npx ts-node hkr-demo/multi_tenant.demo.ts --operation=decrypt --inputFile=<encrypted filepath> --outputFile=<decrypted filepath> --tenant=A
81+
```
82+
83+
### Encrypting with Tenant B
84+
85+
```bash
86+
npx ts-node hkr-demo/multi_tenant.demo.ts --operation=encrypt --inputFile=<data filepath> --outputFile=<encrypted filepath> --tenant=B
87+
```
88+
89+
### Decrypting with Tenant B
90+
91+
```bash
92+
npx ts-node hkr-demo/multi_tenant.demo.ts --operation=decrypt --inputFile=<encrypted filepath> --outputFile=<decrypted filepath> --tenant=B
93+
```
94+
95+
### Example: Demonstrating Tenant Data Isolation
96+
97+
To observe tenant data isolation, run the following commands:
98+
99+
```bash
100+
npx ts-node hkr-demo/multi_tenant.demo.ts --operation=encrypt --inputFile=<data filepath> --outputFile=<encrypted filepath> --tenant=A
101+
npx ts-node hkr-demo/multi_tenant.demo.ts --operation=decrypt --inputFile=<encrypted filepath> --outputFile=<decrypted filepath> --tenant=B
102+
```
103+
104+
An error will occur, demonstrating the isolation between tenant encryption.

modules/example-node/hkr-demo/hkr.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {
2+
buildClient,
3+
CommitmentPolicy,
4+
KeyringNode,
5+
EncryptionContext,
6+
} from '@aws-crypto/client-node'
7+
import { randomBytes } from 'crypto'
8+
import { KMSClient } from '@aws-sdk/client-kms'
9+
import sinon from 'sinon'
10+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
11+
12+
const { encrypt, decrypt } = buildClient(
13+
CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
14+
)
15+
const MAX_INPUT_LENGTH = 20
16+
const MIN_INPUT_LENGTH = 15
17+
const PURPLE_LOG = '\x1b[35m%s\x1b[0m'
18+
const YELLO_LOG = '\x1b[33m%s\x1b[0m'
19+
const GREEN_LOG = '\x1b[32m%s\x1b[0m'
20+
const RED_LOG = '\x1b[31m%s\x1b[0m'
21+
22+
export function generateRandomString(minLength: number, maxLength: number) {
23+
const randomLength =
24+
Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength
25+
return randomBytes(randomLength).toString('hex').slice(0, randomLength)
26+
}
27+
28+
export async function roundtrip(
29+
keyring: KeyringNode,
30+
context: EncryptionContext,
31+
cleartext: string
32+
) {
33+
const { result } = await encrypt(keyring, cleartext, {
34+
encryptionContext: context,
35+
})
36+
37+
const { plaintext, messageHeader } = await decrypt(keyring, result)
38+
39+
const { encryptionContext } = messageHeader
40+
41+
Object.entries(context).forEach(([key, value]) => {
42+
if (encryptionContext[key] !== value) {
43+
throw new Error('Encryption Context does not match expected values')
44+
}
45+
})
46+
47+
return { plaintext, result, cleartext, messageHeader }
48+
}
49+
50+
export async function runRoundTrips(
51+
keyring: KeyringNode,
52+
numRoundTrips: number
53+
) {
54+
const kmsSpy = sinon.spy(KMSClient.prototype, 'send')
55+
const ddbSpy = sinon.spy(DynamoDBClient.prototype, 'send')
56+
const padding = String(numRoundTrips).length
57+
let successes = 0
58+
59+
console.log()
60+
console.log(YELLO_LOG, `${keyring.constructor.name} Roundtrips`) // Print constructor name in yellow
61+
console.time('Total runtime') // Start the timer
62+
63+
for (let i = 0; i < numRoundTrips; i++) {
64+
const encryptionContext = {
65+
roundtrip: i.toString(),
66+
}
67+
const encryptionInput = generateRandomString(
68+
MIN_INPUT_LENGTH,
69+
MAX_INPUT_LENGTH
70+
)
71+
72+
let decryptionOutput: string
73+
try {
74+
const { plaintext } = await roundtrip(
75+
keyring,
76+
encryptionContext,
77+
encryptionInput
78+
)
79+
decryptionOutput = plaintext.toString()
80+
} catch {
81+
decryptionOutput = 'ERROR'
82+
}
83+
84+
const encryptionInputPadding = ' '.repeat(
85+
MAX_INPUT_LENGTH - encryptionInput.length
86+
)
87+
const decryptionOutputPadding = ' '.repeat(
88+
MAX_INPUT_LENGTH - decryptionOutput.length
89+
)
90+
91+
const logMessage = `Roundtrip ${String(i + 1).padStart(
92+
padding,
93+
' '
94+
)}: ${encryptionInput}${encryptionInputPadding} ----encrypt & decrypt----> ${decryptionOutput}${decryptionOutputPadding}`
95+
96+
let logColor: string
97+
if (encryptionInput === decryptionOutput) {
98+
logColor = GREEN_LOG
99+
successes += 1
100+
} else {
101+
logColor = RED_LOG
102+
}
103+
console.log(logColor, logMessage) // Print log message in green
104+
}
105+
106+
console.log()
107+
console.log(YELLO_LOG, `${keyring.constructor.name} metrics`) // Print constructor name in yellow
108+
console.timeEnd('Total runtime')
109+
console.log(PURPLE_LOG, `KMS calls: ${kmsSpy.callCount}`)
110+
console.log(PURPLE_LOG, `DynamoDB calls: ${ddbSpy.callCount}`)
111+
console.log(
112+
PURPLE_LOG,
113+
`Successful roundtrips: ${successes} / ${numRoundTrips}`
114+
)
115+
116+
kmsSpy.restore()
117+
ddbSpy.restore()
118+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// import chalk from 'chalk'
2+
import {
3+
BranchKeyStoreNode,
4+
SrkCompatibilityKmsConfig,
5+
KmsHierarchicalKeyRingNode,
6+
KmsKeyringNode,
7+
} from '@aws-crypto/client-node'
8+
import { runRoundTrips } from './hkr'
9+
import minimist from 'minimist'
10+
11+
const args = minimist(process.argv.slice(2))
12+
const NUM_ROUNDTRIPS = args.numRoundTrips || 10
13+
14+
async function runKmsKeyring() {
15+
const generatorKeyId =
16+
'arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f'
17+
const keyring = new KmsKeyringNode({ generatorKeyId })
18+
19+
await runRoundTrips(keyring, NUM_ROUNDTRIPS)
20+
}
21+
22+
async function runKmsHkrKeyring() {
23+
const branchKeyArn =
24+
'arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126'
25+
const branchKeyId = '2c583585-5770-467d-8f59-b346d0ed1994'
26+
27+
const keyStore = new BranchKeyStoreNode({
28+
ddbTableName: 'KeyStoreDdbTable',
29+
logicalKeyStoreName: 'KeyStoreDdbTable',
30+
kmsConfiguration: new SrkCompatibilityKmsConfig(branchKeyArn),
31+
})
32+
33+
const keyring = new KmsHierarchicalKeyRingNode({
34+
branchKeyId,
35+
keyStore,
36+
cacheLimitTtl: 60,
37+
})
38+
39+
await runRoundTrips(keyring, NUM_ROUNDTRIPS)
40+
}
41+
42+
async function main() {
43+
await runKmsKeyring()
44+
await runKmsHkrKeyring()
45+
}
46+
47+
main()
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import * as fs from 'fs'
2+
import {
3+
BranchKeyStoreNode,
4+
buildClient,
5+
CommitmentPolicy,
6+
KmsHierarchicalKeyRingNode,
7+
SrkCompatibilityKmsConfig,
8+
} from '@aws-crypto/client-node'
9+
import { exit } from 'process'
10+
11+
const { encrypt, decrypt } = buildClient(
12+
CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
13+
)
14+
15+
const branchKeyArn =
16+
'arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126'
17+
const branchKeyId = '38853b56-19c6-4345-9cb5-afc2a25dcdd1'
18+
19+
const keyStore = new BranchKeyStoreNode({
20+
ddbTableName: 'KeyStoreDdbTable',
21+
logicalKeyStoreName: 'KeyStoreDdbTable',
22+
kmsConfiguration: new SrkCompatibilityKmsConfig(branchKeyArn),
23+
})
24+
25+
const keyring = new KmsHierarchicalKeyRingNode({
26+
branchKeyId,
27+
keyStore,
28+
cacheLimitTtl: 60,
29+
})
30+
31+
async function decryptEncryptedData(encryptedData: Buffer) {
32+
const { plaintext: decryptedData, messageHeader } = await decrypt(
33+
keyring,
34+
encryptedData
35+
)
36+
37+
const { encryptionContext } = messageHeader
38+
39+
Object.entries(encryptionContext).forEach(([key, value]) => {
40+
if (encryptionContext[key] !== value) {
41+
throw new Error('Encryption Context does not match expected values')
42+
}
43+
})
44+
45+
return decryptedData
46+
}
47+
48+
async function encryptData(data: Buffer) {
49+
const { result } = await encrypt(keyring, data, {
50+
encryptionContext: { successful: 'demo' },
51+
})
52+
53+
return result
54+
}
55+
56+
async function main() {
57+
const args = process.argv.slice(2)
58+
const operation = args[0]
59+
const inFile = args[1]
60+
const outFile = args[2]
61+
62+
let inData = Buffer.alloc(0)
63+
try {
64+
inData = fs.readFileSync(inFile)
65+
} catch (err) {
66+
console.error(err)
67+
exit(1)
68+
}
69+
70+
let outData: Buffer
71+
let msg: string
72+
if (operation === 'encrypt') {
73+
const data = inData
74+
outData = await encryptData(data)
75+
msg = 'JS has completed encryption'
76+
} else {
77+
const encryptedData = inData
78+
outData = await decryptEncryptedData(encryptedData)
79+
msg = 'JS has completed decryption'
80+
}
81+
82+
try {
83+
fs.writeFileSync(outFile, outData)
84+
} catch (err) {
85+
console.error(err)
86+
exit(1)
87+
}
88+
89+
console.log(msg)
90+
}
91+
92+
main()

0 commit comments

Comments
 (0)