Skip to content

Commit 01c9918

Browse files
committed
feat: integration-node can produce decrypt manifests
From a given encrypt manifest, integration-node now takes `—-decryptManifest` | `-d` This will create a ziped decrypt manifest that can be consumed by decrypt.
1 parent dee213b commit 01c9918

File tree

3 files changed

+210
-21
lines changed

3 files changed

+210
-21
lines changed

modules/integration-node/src/cli.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,13 @@ const cli = yargs
4747
.option('decryptOracle', {
4848
alias: 'o',
4949
describe: 'a url to the decrypt oracle',
50-
demandOption: true,
50+
demandOption: false,
51+
type: 'string',
52+
})
53+
.option('decryptManifest', {
54+
alias: 'd',
55+
describe: 'a file path for to create a decrypt manifest zip file',
56+
demandOption: false,
5157
type: 'string',
5258
})
5359
)
@@ -103,15 +109,17 @@ const cli = yargs
103109
concurrency
104110
)
105111
} else if (command === 'encrypt') {
106-
const { manifestFile, keyFile, decryptOracle } = argv as unknown as {
112+
const { manifestFile, keyFile, decryptOracle, decryptManifest } = argv as unknown as {
107113
manifestFile: string
108114
keyFile: string
109-
decryptOracle: string
115+
decryptOracle?: string,
116+
decryptManifest?: string,
110117
}
111118
result = await integrationEncryptTestVectors(
112119
manifestFile,
113120
keyFile,
114121
decryptOracle,
122+
decryptManifest,
115123
tolerateFailures,
116124
testName,
117125
concurrency

modules/integration-node/src/get_encrypt_test_iterator.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,42 @@ import {
1414
import { URL } from 'url'
1515
import { readFileSync } from 'fs'
1616
import got from 'got'
17+
import { ZipFile } from 'yazl'
1718

1819
export async function getEncryptTestVectorIterator(
1920
manifestFile: string,
20-
keyFile: string
21+
keyFile: string,
22+
manifestZip?: ZipFile,
2123
) {
2224
const [manifest, keys]: [EncryptManifestList, KeyList] = await Promise.all([
2325
getParsedJSON(manifestFile),
2426
getParsedJSON(keyFile),
2527
])
2628

27-
return _getEncryptTestVectorIterator(manifest, keys)
29+
return _getEncryptTestVectorIterator(manifest, keys, manifestZip)
2830
}
2931

3032
/* Just a simple more testable function */
3133
export function _getEncryptTestVectorIterator(
3234
{ tests, plaintexts }: EncryptManifestList,
33-
{ keys }: KeyList
35+
keysManifest: KeyList,
36+
manifestZip?: ZipFile,
3437
) {
38+
39+
if (manifestZip) {
40+
// We assume that the keys manifest given for encrypt
41+
// has all the keys required for decrypt.
42+
manifestZip.addBuffer(Buffer.from(JSON.stringify(keysManifest)), `keys.json`)
43+
}
44+
const { keys } = keysManifest
3545
const plaintextBytes: { [name: string]: Buffer } = {}
3646

3747
Object.keys(plaintexts).forEach((name) => {
3848
plaintextBytes[name] = randomBytes(plaintexts[name])
49+
50+
if (manifestZip) {
51+
manifestZip.addBuffer(plaintextBytes[name], `plaintexts/${name}`)
52+
}
3953
})
4054

4155
return (function* nextTest(): IterableIterator<EncryptTestVectorInfo> {
@@ -60,6 +74,7 @@ export function _getEncryptTestVectorIterator(
6074
name,
6175
keysInfo,
6276
plainTextData: plaintextBytes[plaintext],
77+
plaintextName: plaintext,
6378
encryptOp: { suiteId, frameLength, encryptionContext },
6479
}
6580
}
@@ -70,6 +85,7 @@ export interface EncryptTestVectorInfo {
7085
name: string
7186
keysInfo: KeyInfoTuple[]
7287
plainTextData: Buffer
88+
plaintextName: string
7389
encryptOp: {
7490
suiteId: AlgorithmSuiteIdentifier
7591
frameLength: number

modules/integration-node/src/integration_tests.ts

Lines changed: 180 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
TestVectorResult,
77
parseIntegrationTestVectorsToTestVectorIterator,
88
PositiveTestVectorInfo,
9+
DecryptManifestList,
910
} from '@aws-crypto/integration-vectors'
1011
import {
1112
EncryptTestVectorInfo,
@@ -28,6 +29,9 @@ import streamToPromise from 'stream-to-promise'
2829
const { encrypt, decrypt, decryptUnsignedMessageStream } = buildClient(
2930
CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT
3031
)
32+
import { ZipFile } from 'yazl'
33+
import { createWriteStream } from 'fs'
34+
import { v4 } from 'uuid'
3135
import * as stream from 'stream'
3236
import * as util from 'util'
3337
const pipeline = util.promisify(stream.pipeline)
@@ -111,11 +115,28 @@ async function testPositiveDecryptVector(
111115
}
112116
}
113117

114-
// This is only viable for small streams, if we start get get larger streams, an stream equality should get written
118+
interface ProcessEncryptResults {
119+
handleEncryptResult: HandleEncryptResult
120+
// We need to have a done step to close the ZipFile.
121+
done(): void
122+
// The handleEncryptResult needs the ZipFile
123+
// so that it can add ciphertexts and tests.
124+
// But when we set up the encrypt manifest,
125+
// we create plaintext files and have the keys manifest.
126+
// This is a quick and dirty way to share the ZipFile
127+
// between these two places.
128+
manifestZip?: ZipFile
129+
}
130+
131+
interface HandleEncryptResult {
132+
(encryptResult: Buffer, info: EncryptTestVectorInfo): Promise<boolean>
133+
}
134+
115135
export async function testEncryptVector(
116-
{ name, keysInfo, encryptOp, plainTextData }: EncryptTestVectorInfo,
117-
decryptOracle: string
136+
info: EncryptTestVectorInfo,
137+
handleEncryptResult: HandleEncryptResult
118138
): Promise<TestVectorResult> {
139+
const { name, keysInfo, encryptOp, plainTextData } = info
119140
try {
120141
const cmm = encryptMaterialsManagerNode(keysInfo)
121142
const { result: encryptResult } = await encrypt(
@@ -124,7 +145,68 @@ export async function testEncryptVector(
124145
encryptOp
125146
)
126147

127-
const decryptResponse = await got.post(decryptOracle, {
148+
const result = await handleEncryptResult(encryptResult, info)
149+
return { result, name }
150+
} catch (err) {
151+
return { result: false, name, err }
152+
}
153+
}
154+
155+
// This isolates the logic on how to do both.
156+
// Right now we only have 2 ways to handle results
157+
// so this seems reasonable.
158+
function composeEncryptResults(
159+
decryptOracle?: string,
160+
decryptManifest?: string
161+
): ProcessEncryptResults {
162+
if (!!decryptOracle && !!decryptManifest) {
163+
const oracle = decryptOracleEncryptResults(decryptOracle)
164+
const manifest = decryptionManifestEncryptResults(decryptManifest)
165+
166+
return {
167+
done() {
168+
manifest.done()
169+
oracle.done()
170+
},
171+
172+
async handleEncryptResult(
173+
encryptResult: Buffer,
174+
info: EncryptTestVectorInfo
175+
): Promise<boolean> {
176+
return Promise.all([
177+
oracle.handleEncryptResult(encryptResult, info),
178+
manifest.handleEncryptResult(encryptResult, info),
179+
]).then((results) => {
180+
const [oracleResult, manifestResult] = results
181+
return oracleResult && manifestResult
182+
})
183+
},
184+
manifestZip: manifest.manifestZip,
185+
}
186+
} else if (!!decryptOracle) {
187+
return decryptOracleEncryptResults(decryptOracle)
188+
} else if (!!decryptManifest) {
189+
return decryptionManifestEncryptResults(decryptManifest)
190+
}
191+
needs(false, 'unsupported')
192+
}
193+
194+
function decryptOracleEncryptResults(
195+
decryptOracle: string
196+
): ProcessEncryptResults {
197+
const decryptOracleUrl = new URL(decryptOracle).toString()
198+
return {
199+
handleEncryptResult,
200+
// There is nothing to do when the oracle is done
201+
// since nothing is saved.
202+
done: () => {},
203+
}
204+
205+
async function handleEncryptResult(
206+
encryptResult: Buffer,
207+
info: EncryptTestVectorInfo
208+
): Promise<boolean> {
209+
const decryptResponse = await got.post(decryptOracleUrl, {
128210
headers: {
129211
'Content-Type': 'application/octet-stream',
130212
Accept: 'application/octet-stream',
@@ -134,10 +216,69 @@ export async function testEncryptVector(
134216
})
135217
needs(decryptResponse.statusCode === 200, 'decrypt failure')
136218
const { body } = decryptResponse
137-
const result = plainTextData.equals(body)
138-
return { result, name }
139-
} catch (err) {
140-
return { result: false, name, err }
219+
// This is only viable for small streams,
220+
// if we start get get larger streams,
221+
// a stream equality should get written
222+
return info.plainTextData.equals(body)
223+
}
224+
}
225+
226+
function decryptionManifestEncryptResults(
227+
manifestPath: string
228+
): ProcessEncryptResults {
229+
const manifestZip = new ZipFile()
230+
const manifest: DecryptManifestList = {
231+
manifest: {
232+
type: 'awses-decrypt',
233+
version: 1,
234+
},
235+
client: {
236+
name: 'aws/aws-encryption-sdk-javascript',
237+
//Get the right version
238+
version: '2.2.0',
239+
},
240+
keys: 'file://keys.json',
241+
tests: {},
242+
}
243+
manifestZip.outputStream.pipe(createWriteStream(manifestPath))
244+
245+
return {
246+
handleEncryptResult,
247+
done: () => {
248+
// All the tests have completed,
249+
// so we write the manifest,
250+
// as close the zip file.
251+
manifestZip.addBuffer(
252+
Buffer.from(JSON.stringify(manifest)),
253+
`manifest.json`
254+
)
255+
manifestZip.end()
256+
},
257+
manifestZip,
258+
}
259+
260+
async function handleEncryptResult(
261+
encryptResult: Buffer,
262+
info: EncryptTestVectorInfo
263+
): Promise<boolean> {
264+
const testName = v4()
265+
266+
manifestZip.addBuffer(encryptResult, `ciphertexts/${testName}`)
267+
268+
manifest.tests[testName] = {
269+
description: `Decrypt vector from ${info.name}`,
270+
ciphertext: `file://ciphertexts/${testName}`,
271+
'master-keys': info.keysInfo.map((info) => info[0]),
272+
result: {
273+
output: {
274+
plaintext: `file://plaintexts/${info.plaintextName}`,
275+
},
276+
},
277+
}
278+
279+
// These files are tested on decrypt
280+
// so there is nothing to test at this point.
281+
return true
141282
}
142283
}
143284

@@ -196,22 +337,42 @@ export async function integrationDecryptTestVectors(
196337
export async function integrationEncryptTestVectors(
197338
manifestFile: string,
198339
keyFile: string,
199-
decryptOracle: string,
340+
decryptOracle?: string,
341+
decryptManifest?: string,
200342
tolerateFailures = 0,
201343
testName?: string,
202344
concurrency = 1
203345
): Promise<number> {
204-
const decryptOracleUrl = new URL(decryptOracle).toString()
205-
const tests = await getEncryptTestVectorIterator(manifestFile, keyFile)
346+
needs(
347+
!!decryptOracle || !!decryptManifest,
348+
'Need to pass an oracle or manifest path.'
349+
)
206350

207-
return parallelTests(concurrency, tolerateFailures, runTest, tests)
351+
const { done, handleEncryptResult, manifestZip } = composeEncryptResults(
352+
decryptOracle,
353+
decryptManifest
354+
)
355+
356+
const tests = await getEncryptTestVectorIterator(
357+
manifestFile,
358+
keyFile,
359+
manifestZip
360+
)
361+
362+
return parallelTests(concurrency, tolerateFailures, runTest, tests).then(
363+
(num) => {
364+
// Do the output processing here
365+
done()
366+
return num
367+
}
368+
)
208369

209370
async function runTest(test: EncryptTestVectorInfo): Promise<boolean> {
210371
if (testName) {
211372
if (test.name !== testName) return true
212373
}
213374
return handleTestResults(
214-
await testEncryptVector(test, decryptOracleUrl),
375+
await testEncryptVector(test, handleEncryptResult),
215376
notSupportedEncryptMessages
216377
)
217378
}
@@ -248,8 +409,12 @@ async function parallelTests<
248409
* we just process the value and ask for another.
249410
* Which will return done as true again.
250411
*/
251-
if (!value && done) return _resolve(failureCount)
252-
412+
if (!value && done) {
413+
// We are done enqueueing work,
414+
// but we need to wait until all that work is done
415+
Promise.all([...queue]).then(() => _resolve(failureCount))
416+
return
417+
}
253418
/* I need to define the work to be enqueue
254419
* and a way to dequeue this work when complete.
255420
* A Set of promises works nicely.

0 commit comments

Comments
 (0)