Skip to content

Commit 043506d

Browse files
authored
feat: Encryption tests for integration-browser (#159)
https://github.com/awslabs/aws-crypto-tools-test-vector-framework defines an encryption manifest. This manifest should be used to maintain cross compatibility of language implementations. Then integrate this into the CI integration tests. Also update package.json to better organize parameters.
1 parent ea72904 commit 043506d

10 files changed

+388
-74
lines changed

modules/integration-browser/karma.conf.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@ module.exports = function (config) {
66
basePath: '',
77
frameworks: ['jasmine'],
88
files: [
9-
'fixtures/tests.json',
9+
'fixtures/decrypt_tests.json',
10+
'fixtures/encrypt_tests.json',
11+
'fixtures/decrypt_oracle.json',
1012
{ pattern: 'fixtures/*.json', included: false, served: true, watched: false, nocache: true },
11-
'build/module/integration.test.js'
13+
'build/module/integration.decrypt.test.js',
14+
'build/module/integration.encrypt.test.js',
1215
],
1316
preprocessors: {
14-
'build/module/integration.test.js': ['webpack', 'credentials'],
15-
'./fixtures/tests.json': ['json_fixtures']
17+
'build/module/integration.decrypt.test.js': ['webpack', 'credentials'],
18+
'build/module/integration.encrypt.test.js': ['webpack', 'credentials'],
19+
'./fixtures/decrypt_tests.json': ['json_fixtures'],
20+
'./fixtures/encrypt_tests.json': ['json_fixtures'],
21+
'./fixtures/decrypt_oracle.json': ['json_fixtures']
22+
1623
},
1724
webpack: {
1825
mode: 'development',

modules/integration-browser/package.json

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,25 @@
1818
"@aws-crypto/client-browser": "^0.1.0-preview.2",
1919
"@aws-sdk/karma-credential-loader": "0.1.0-preview.2",
2020
"@aws-sdk/util-base64-browser": "0.1.0-preview.1",
21+
"@aws-sdk/util-utf8-browser": "0.1.0-preview.1",
2122
"@trust/keyto": "^0.3.7",
23+
"@types/got": "^9.6.2",
24+
"@types/stream-to-promise": "^2.2.0",
2225
"@types/unzipper": "^0.9.1",
2326
"@types/yargs": "^13.0.0",
24-
"stream-to-promise": "^2.2.0",
25-
"tslib": "^1.9.3",
26-
"unzipper": "^0.9.11",
27-
"yargs": "^13.2.2",
27+
"got": "^9.6.0",
2828
"jasmine-core": "^3.4.0",
2929
"karma": "^4.1.0",
3030
"karma-chrome-launcher": "^2.2.0",
3131
"karma-jasmine": "^2.0.1",
3232
"karma-json-fixtures-preprocessor": "0.0.6",
3333
"karma-webpack": "^3.0.5",
34+
"puppeteer": "^1.14.0",
35+
"stream-to-promise": "^2.2.0",
36+
"tslib": "^1.9.3",
37+
"unzipper": "^0.9.11",
3438
"webpack": "^4.30.0",
35-
"puppeteer": "^1.14.0"
39+
"yargs": "^13.2.2"
3640
},
3741
"devDependencies": {
3842
"@types/node": "^11.11.4",
@@ -46,10 +50,9 @@
4650
"main": "./build/main/index.js",
4751
"module": "./build/module/index.js",
4852
"types": "./build/main/index.d.ts",
49-
"bin": "./build_fixtures",
53+
"bin": "./build/main/cli.js",
5054
"files": [
5155
"build/**/*",
52-
"build_fixtures",
5356
"karma.conf.js"
5457
],
5558
"standard": {

modules/integration-browser/build_fixtures renamed to modules/integration-browser/src/build_decrypt_fixtures.ts

Lines changed: 23 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,51 +14,28 @@
1414
* limitations under the License.
1515
*/
1616

17-
const argv = require('yargs')
18-
.option('vectorFile', {
19-
alias: 'v',
20-
describe: 'a vector zip file from aws-encryption-sdk-test-vectors',
21-
demandOption: true,
22-
type: 'string'
23-
})
24-
.option('testName', {
25-
alias: 't',
26-
describe: 'an optional test name to execute',
27-
type: 'string'
28-
})
29-
.option('slice', {
30-
alias: 's',
31-
describe: 'an optional range start:end e.g. 100:200',
32-
type: 'string'
33-
})
34-
.options('karma', {
35-
alias: 'k',
36-
describe: 'start karma and run the tests',
37-
type: 'boolean'
38-
})
39-
.argv
40-
41-
const { vectorFile, testName, slice, karma } = argv
42-
const {Open} = require('unzipper')
43-
const streamToPromise = require('stream-to-promise')
44-
const fs = require('fs')
45-
const path = require('path')
46-
const { spawnSync } = require('child_process')
47-
const fixtures = path.join(__dirname, './fixtures')
48-
49-
const [start=0, end=9999] = (slice || '').split(':').map(n => parseInt(n, 10))
50-
51-
if (!fs.existsSync(fixtures)){
52-
fs.mkdirSync(fixtures)
53-
}
17+
import { Open } from 'unzipper'
18+
import streamToPromise from 'stream-to-promise'
19+
import { writeFileSync } from 'fs'
20+
21+
import { DecryptManifestList } from './types' // eslint-disable-line no-unused-vars
22+
23+
/* This function interacts with manifest information
24+
* and produces the fixtures in the `fixtures`
25+
* that the karma server will consume to run tests.
26+
* This gives us 2 useful freedoms.
27+
* 1. The code is not tied to a specific copy of the manifest information
28+
* 2. The tests can be run on a subset of tests for debugging.
29+
*/
30+
export async function buildDecryptFixtures (fixtures: string, vectorFile: string, testName: string, slice: string) {
31+
const [start = 0, end = 9999] = (slice || '').split(':').map(n => parseInt(n, 10))
5432

55-
;(async () => {
5633
const centralDirectory = await Open.file(vectorFile)
5734
const filesMap = new Map(centralDirectory.files.map(file => [file.path, file]))
5835

5936
const readUriOnce = (() => {
6037
const cache = new Map()
61-
return async (uri) => {
38+
return async (uri: string) => {
6239
const has = cache.get(uri)
6340
if (has) return has
6441
const fileInfo = filesMap.get(testUri2Path(uri))
@@ -70,7 +47,7 @@ if (!fs.existsSync(fixtures)){
7047
})()
7148

7249
const manifestBuffer = await readUriOnce('manifest.json')
73-
const { keys: keysFile, tests } = JSON.parse(manifestBuffer.toString('utf8'))
50+
const { keys: keysFile, tests }: DecryptManifestList = JSON.parse(manifestBuffer.toString('utf8'))
7451
const keysBuffer = await readUriOnce(keysFile)
7552
const { keys } = JSON.parse(keysBuffer.toString('utf8'))
7653
const testNames = []
@@ -94,8 +71,8 @@ if (!fs.existsSync(fixtures)){
9471
const plainTextInfo = filesMap.get(testUri2Path(plaintextFile))
9572
const cipherInfo = filesMap.get(testUri2Path(ciphertext))
9673
if (!cipherInfo || !plainTextInfo) throw new Error(`no file for ${name}: ${ciphertext} | ${plaintextFile}`)
97-
98-
const cipherText = await streamToPromise(cipherInfo.stream())
74+
75+
const cipherText = await streamToPromise(<NodeJS.ReadableStream>cipherInfo.stream())
9976
const plainText = await readUriOnce(plainTextInfo.path)
10077
const keysInfo = masterKeys.map(keyInfo => {
10178
const key = keys[keyInfo.key]
@@ -111,19 +88,12 @@ if (!fs.existsSync(fixtures)){
11188
plainText: plainText.toString('base64')
11289
})
11390

114-
fs.writeFileSync(`${fixtures}/${name}.json`, test)
91+
writeFileSync(`${fixtures}/${name}.json`, test)
11592
}
11693

117-
fs.writeFileSync(`${fixtures}/tests.json`, JSON.stringify(testNames))
118-
119-
if (karma) {
120-
spawnSync('npm', ['run', 'karma'], {
121-
cwd: __dirname,
122-
stdio: 'inherit'
123-
})
124-
}
125-
})()
94+
writeFileSync(`${fixtures}/decrypt_tests.json`, JSON.stringify(testNames))
95+
}
12696

127-
function testUri2Path (uri) {
97+
function testUri2Path (uri: string) {
12898
return uri.replace('file://', '')
12999
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use
5+
* this file except in compliance with the License. A copy of the License is
6+
* located at
7+
*
8+
* http://aws.amazon.com/apache2.0/
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
* implied. See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import {
17+
EncryptManifestList, // eslint-disable-line no-unused-vars
18+
KeyList, // eslint-disable-line no-unused-vars
19+
KeyInfoTuple // eslint-disable-line no-unused-vars
20+
} from './types'
21+
import { randomBytes } from 'crypto'
22+
import {
23+
AlgorithmSuiteIdentifier, // eslint-disable-line no-unused-vars
24+
EncryptionContext // eslint-disable-line no-unused-vars
25+
} from '@aws-crypto/client-browser'
26+
import { URL } from 'url'
27+
import { readFileSync, writeFileSync } from 'fs'
28+
import got from 'got'
29+
30+
/* This function interacts with manifest information
31+
* and produces the fixtures in the `fixtures`
32+
* that the karma server will consume to run tests.
33+
* This gives us 2 useful freedoms.
34+
* 1. The code is not tied to a specific copy of the manifest information
35+
* 2. The tests can be run on a subset of tests for debugging.
36+
*/
37+
export async function buildEncryptFixtures (fixtures: string, manifestFile: string, keyFile: string, testName: string, slice: string) {
38+
const [start = 0, end = 9999] = (slice || '').split(':').map(n => parseInt(n, 10))
39+
const { tests, plaintexts }: EncryptManifestList = await getParsedJSON(manifestFile)
40+
const { keys }: KeyList = await getParsedJSON(keyFile)
41+
42+
const plaintextBytes: {[name: string]: string} = {}
43+
44+
Object
45+
.keys(plaintexts)
46+
.forEach(name => {
47+
/* Generate random bites as per spec.
48+
* See: https://github.com/awslabs/aws-crypto-tools-test-vector-framework/blob/master/features/0003-awses-message-encryption.md#plaintexts
49+
*/
50+
plaintextBytes[name] = randomBytes(10).toString('base64')
51+
})
52+
53+
const testNames = []
54+
let count = 0
55+
56+
for (const [name, testInfo] of Object.entries(tests)) {
57+
count += 1
58+
59+
if (testName) {
60+
if (name !== testName) continue
61+
}
62+
63+
if (slice) {
64+
if (start >= count) continue
65+
if (count > end) continue
66+
}
67+
68+
testNames.push(name)
69+
70+
const {
71+
plaintext,
72+
'master-keys': masterKeys,
73+
algorithm,
74+
'frame-size': frameLength,
75+
'encryption-context': encryptionContext
76+
} = testInfo
77+
78+
const keysInfo = <KeyInfoTuple[]>masterKeys.map(keyInfo => {
79+
const key = keys[keyInfo.key]
80+
if (!key) throw new Error(`no key for ${name}`)
81+
return [keyInfo, key]
82+
})
83+
84+
/* I'm expecting that the encrypt function will throw if this is not a supported AlgorithmSuiteIdentifier */
85+
const suiteId = <AlgorithmSuiteIdentifier>parseInt(algorithm, 16)
86+
87+
const test: EncryptTestVectorInfo = {
88+
name,
89+
keysInfo,
90+
plainTextData: plaintextBytes[plaintext],
91+
encryptOp: { suiteId, frameLength, encryptionContext }
92+
}
93+
94+
writeFileSync(`${fixtures}/${name}.json`, JSON.stringify(test))
95+
}
96+
97+
writeFileSync(`${fixtures}/encrypt_tests.json`, JSON.stringify(testNames))
98+
}
99+
100+
export interface EncryptTestVectorInfo {
101+
name: string,
102+
keysInfo: KeyInfoTuple[],
103+
plainTextData: string,
104+
encryptOp: {
105+
suiteId: AlgorithmSuiteIdentifier,
106+
frameLength: number,
107+
encryptionContext: EncryptionContext
108+
}
109+
}
110+
111+
async function getParsedJSON (thing: string) {
112+
try {
113+
const url = new URL(thing)
114+
if (url.protocol === 'file:') {
115+
return jsonAtPath(thing)
116+
} else {
117+
return jsonAtUrl(url)
118+
}
119+
} catch (ex) {
120+
return jsonAtPath(thing)
121+
}
122+
}
123+
async function jsonAtUrl (url: URL) {
124+
const { body } = await got(url)
125+
return JSON.parse(body)
126+
}
127+
128+
function jsonAtPath (path: string) {
129+
const json = readFileSync(path, { encoding: 'utf-8' })
130+
return JSON.parse(json)
131+
}

0 commit comments

Comments
 (0)