Skip to content

Commit 896883a

Browse files
authored
fix: encrypt/decrypt node (aws#133)
Small frames exacerbate a race between VerifyStream and DecipherStream. Once an `AuthTag` event is sent to DecipherStream the current frame is decrypted and verified before being flushed. More frame data will not be processed by DecipherStream because DecipherStream will not execute the _transform callback until the frame is flushed. However nothing stops VerifyStream from continuing to process data. VerifyStream will push the next frame's data, which will be cached by DecipherStream. But once VerifyStream reaches the end of the frame it will emit `AuthTag`. This means the DecipherStream will receive the AuthTag before the frame data and it will correctly fail on precondition checks. To resolve this a simple callback is passed from VerifyStream to DecipherStream in the `AuthTag` event. Tests for both encrypt and decrypt are included here, because that is how this issue was found.
1 parent 8a4d708 commit 896883a

File tree

9 files changed

+333
-74
lines changed

9 files changed

+333
-74
lines changed

modules/decrypt-node/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,17 @@
2424
"tslib": "^1.9.3"
2525
},
2626
"devDependencies": {
27-
"@aws-crypto/encrypt-node": "^0.1.0-preview.1",
2827
"@types/chai": "^4.1.4",
2928
"@types/mocha": "^5.2.5",
3029
"@types/node": "^11.11.4",
3130
"@typescript-eslint/eslint-plugin": "^1.9.0",
3231
"@typescript-eslint/parser": "^1.9.0",
32+
"@types/from2": "^2.3.0",
3333
"chai": "^4.1.2",
3434
"mocha": "^5.2.0",
3535
"nyc": "^14.0.0",
3636
"standard": "^12.0.1",
37+
"from2": "^2.3.0",
3738
"ts-node": "^7.0.1",
3839
"typescript": "^3.5.0"
3940
},

modules/decrypt-node/src/decipher_stream.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ export function getDecipherStream () {
6868
decipherInfo = info
6969
})
7070
.on('BodyInfo', this._onBodyHeader)
71-
.on('AuthTag', async (authTag: Buffer) => {
71+
.on('AuthTag', async (authTag: Buffer, next: Function) => {
7272
try {
73-
await this._onAuthTag(authTag)
73+
await this._onAuthTag(authTag, next)
7474
} catch (e) {
7575
this.emit('error', e)
7676
}
@@ -134,7 +134,7 @@ export function getDecipherStream () {
134134
super._read(size)
135135
}
136136

137-
_onAuthTag = async (authTag: Buffer) => {
137+
_onAuthTag = async (authTag: Buffer, next:Function) => {
138138
const { decipher, content, contentLength } = decipherState
139139
/* Precondition: _onAuthTag must be called only after a frame has been accumulated.
140140
* However there is an edge case. The final frame _can_ be zero length.
@@ -169,6 +169,17 @@ export function getDecipherStream () {
169169
}
170170
}
171171

172+
/* This frame is complete.
173+
* Need to notify the VerifyStream continue.
174+
* See the note in `AuthTag` for details.
175+
* The short answer is that for small frame sizes,
176+
* the "next" frame associated auth tag may be
177+
* parsed and send before the "current" is processed.
178+
* This will cause the auth tag event to fire before
179+
* any _transform events fire and a 'Lengths do not match' precondition to fail.
180+
*/
181+
next()
182+
172183
// This frame is complete. Notify _transform to continue, see needs above for more details
173184
if (frameComplete) frameComplete()
174185
// reset for next frame.

modules/decrypt-node/src/decrypt.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export interface DecryptOptions {
3636

3737
export async function decrypt (
3838
cmm: NodeMaterialsManager|KeyringNode,
39-
ciphertext: Buffer|Uint8Array|Readable|string,
39+
ciphertext: Buffer|Uint8Array|Readable|string|NodeJS.ReadableStream,
4040
{ encoding, maxBodySize } : DecryptOptions = {}
4141
): Promise<DecryptOutput> {
4242
const stream = decryptStream(cmm, { maxBodySize })

modules/decrypt-node/src/verify_stream.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,6 @@ export class VerifyStream extends PortableTransformWithType {
121121
if (this._verify) {
122122
this._verify.update(frameBuffer.slice(0, frameHeader.readPos))
123123
}
124-
// clear the buffer. It _could_ have cipher text...
125-
state.buffer = Buffer.alloc(0)
126124
const tail = chunk.slice(frameHeader.readPos)
127125
this.emit('BodyInfo', frameHeader)
128126
state.currentFrame = frameHeader
@@ -155,23 +153,49 @@ export class VerifyStream extends PortableTransformWithType {
155153
state.authTagBuffer = Buffer.concat([authTagBuffer, chunk])
156154
return callback()
157155
} else {
158-
state.authTagBuffer = Buffer.concat([authTagBuffer, chunk], tagLengthBytes)
156+
const finalAuthTagBuffer = Buffer.concat([authTagBuffer, chunk], tagLengthBytes)
159157
if (this._verify) {
160-
this._verify.update(state.authTagBuffer)
158+
this._verify.update(finalAuthTagBuffer)
161159
}
162-
this.emit('AuthTag', state.authTagBuffer)
163-
const tail = chunk.slice(left)
164-
if (!currentFrame.isFinalFrame) {
165-
state.buffer = Buffer.alloc(0)
166-
state.currentFrame = undefined
167-
state.authTagBuffer = Buffer.alloc(0)
160+
/* Reset state.
161+
* Ciphertext buffers and authTag buffers need to be cleared.
162+
*/
163+
state.buffer = Buffer.alloc(0)
164+
state.currentFrame = undefined
165+
state.authTagBuffer = Buffer.alloc(0)
166+
/* After the final frame the file format is _much_ simpler.
167+
* Making sure the cascading if blocks fall to the signature can be tricky and brittle.
168+
* After the final frame, just moving on to concatenate the signature is much simpler.
169+
*/
170+
if (currentFrame.isFinalFrame) {
171+
/* Overwriting the _transform function.
172+
* Data flow control is not handled here.
173+
*/
174+
this._transform = (chunk: Buffer, _enc: string, callback: Function) => {
175+
if (chunk.length) {
176+
state.signatureInfo = Buffer.concat([state.signatureInfo, chunk])
177+
}
178+
179+
callback()
180+
}
168181
}
169-
return setImmediate(() => this._transform(tail, enc, callback))
170-
}
171-
}
172182

173-
if (chunk.length) {
174-
state.signatureInfo = Buffer.concat([state.signatureInfo, chunk])
183+
const tail = chunk.slice(left)
184+
/* The decipher_stream uses the `AuthTag` event to flush the accumulated frame.
185+
* This is because ciphertext should never be returned until it is verified.
186+
* i.e. the auth tag checked.
187+
* This can create an issue if the chucks and frame size are small.
188+
* If the verify stream continues processing and sends the next auth tag,
189+
* before the current auth tag has been completed.
190+
* This is basically a back pressure issue.
191+
* Since the frame size, and consequently the high water mark,
192+
* can not be know when the stream is created,
193+
* the internal stream state would need to be modified.
194+
* I assert that a simple callback is a simpler way to handle this.
195+
*/
196+
const next = () => this._transform(tail, enc, callback)
197+
return this.emit('AuthTag', finalAuthTagBuffer, next)
198+
}
175199
}
176200

177201
callback()

modules/decrypt-node/test/decrypt.test.ts

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,54 @@
1717

1818
import { expect } from 'chai'
1919
import 'mocha'
20-
import {
21-
NodeDecryptionMaterial, // eslint-disable-line no-unused-vars
22-
NodeEncryptionMaterial, // eslint-disable-line no-unused-vars
23-
KeyringNode, EncryptedDataKey,
24-
KeyringTraceFlag, AlgorithmSuiteIdentifier
25-
} from '@aws-crypto/material-management-node'
26-
27-
// import * as fs from 'fs'
28-
29-
import { encrypt } from '@aws-crypto/encrypt-node'
30-
import { decrypt } from '../src/decrypt'
31-
32-
describe('simple', () => {
33-
it('decrypt what I encrypt', async () => {
34-
class TestKeyring extends KeyringNode {
35-
async _onEncrypt (material: NodeEncryptionMaterial) {
36-
const unencryptedDataKey = new Uint8Array(material.suite.keyLengthBytes).fill(1)
37-
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_GENERATED_DATA_KEY }
38-
const edk = new EncryptedDataKey({ providerId: 'k', providerInfo: 'k', encryptedDataKey: new Uint8Array(3) })
39-
return material
40-
.setUnencryptedDataKey(unencryptedDataKey, trace)
41-
.addEncryptedDataKey(edk, KeyringTraceFlag.WRAPPING_KEY_ENCRYPTED_DATA_KEY)
42-
}
43-
async _onDecrypt (material: NodeDecryptionMaterial) {
44-
const unencryptedDataKey = new Uint8Array(material.suite.keyLengthBytes).fill(1)
45-
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY }
46-
return material.setUnencryptedDataKey(unencryptedDataKey, trace)
47-
}
48-
}
49-
50-
const keyRing = new TestKeyring()
51-
const suiteId = AlgorithmSuiteIdentifier.ALG_AES128_GCM_IV12_TAG16
52-
53-
const plaintext = 'asdf'
54-
const { ciphertext } = await encrypt(keyRing, plaintext, { suiteId })
55-
56-
const { plaintext: test, messageHeader } = await decrypt(keyRing, ciphertext)
57-
58-
expect(messageHeader.suiteId).to.equal(suiteId)
59-
expect(test.toString()).to.equal(plaintext)
20+
import { AlgorithmSuiteIdentifier } from '@aws-crypto/material-management-node'
21+
import { decrypt } from '../src/index'
22+
import * as fixtures from './fixtures'
23+
import from from 'from2'
24+
25+
describe('decrypt', () => {
26+
it('string with encoding', async () => {
27+
const { plaintext: test, messageHeader } = await decrypt(
28+
fixtures.decryptKeyring(),
29+
fixtures.base64CiphertextAlgAes256GcmIv12Tag16HkdfSha384EcdsaP384(),
30+
{ encoding: 'base64' }
31+
)
32+
33+
expect(messageHeader.suiteId).to.equal(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384)
34+
expect(messageHeader.encryptionContext).to.deep.equal(fixtures.encryptionContext())
35+
expect(test.toString('base64')).to.equal(fixtures.base64Plaintext())
36+
})
37+
38+
it('buffer', async () => {
39+
const { plaintext: test, messageHeader } = await decrypt(
40+
fixtures.decryptKeyring(),
41+
Buffer.from(fixtures.base64CiphertextAlgAes256GcmIv12Tag16HkdfSha384EcdsaP384(), 'base64')
42+
)
43+
44+
expect(messageHeader.suiteId).to.equal(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384)
45+
expect(messageHeader.encryptionContext).to.deep.equal(fixtures.encryptionContext())
46+
expect(test.toString('base64')).to.equal(fixtures.base64Plaintext())
47+
})
48+
49+
it('stream', async () => {
50+
const ciphertext = Buffer.from(fixtures.base64CiphertextAlgAes256GcmIv12Tag16HkdfSha384EcdsaP384(), 'base64')
51+
const i = ciphertext.values()
52+
const ciphertextStream = from((_: number, next: Function) => {
53+
/* Pushing 1 byte at time is the most annoying thing.
54+
* This is done intentionally to hit _every_ boundary condition.
55+
*/
56+
const { value, done } = i.next()
57+
if (done) return next(null, null)
58+
next(null, new Uint8Array([value]))
59+
})
60+
61+
const { plaintext: test, messageHeader } = await decrypt(
62+
fixtures.decryptKeyring(),
63+
ciphertextStream
64+
)
65+
66+
expect(messageHeader.suiteId).to.equal(AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384)
67+
expect(messageHeader.encryptionContext).to.deep.equal(fixtures.encryptionContext())
68+
expect(test.toString('base64')).to.equal(fixtures.base64Plaintext())
6069
})
6170
})

modules/decrypt-node/test/fixtures.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
/* eslint-env mocha */
17+
18+
import {
19+
NodeDecryptionMaterial, // eslint-disable-line no-unused-vars
20+
NodeEncryptionMaterial, // eslint-disable-line no-unused-vars
21+
KeyringNode,
22+
KeyringTraceFlag
23+
} from '@aws-crypto/material-management-node'
24+
25+
export function base64CiphertextAlgAes256GcmIv12Tag16HkdfSha384EcdsaP384 () {
26+
return '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'
27+
}
28+
29+
export function base64Plaintext () {
30+
return '3Ye0RVTIjYp9Yvi+81Dzq9h9gAUF6akM1mqTbPKMhmwgxTWuj6Wlf8UFUMG7zALPDpN77EleMS3dXUOGlr/nalmwXkBseEo+QxCJgeo6WMuB2xQHZqJT+0glM3mcl2FWwiQDZ9G84dYOW1KSDfiyISe9rTqARl0fmEnD1oB6zlP4cYg7+DDTxOOvw5RndoiOBZ+mLbZT9vHTsJkWB3HgFO06dFAtwSgjUAEaNWMjk04vIT+9SBvql6cOk+GLfUdkH33chNk22yKPF2UQ6+lvW0YqGODIfTBQXypPuuKYXJ3T583YxeiKoxuxZFpVNkg30r5cYPOYulINy+YrWQIbNFRP9Zk0CNkAJ7zsIMhQ8IXH+zG1bQmwh1RDGSAfZhmsR147Jsi6qty9Fe9O'
31+
}
32+
33+
export function encryptionContext () {
34+
return {
35+
'aws-crypto-public-key': 'AnYtFEewZm+28KhIGpx8RhkaUaLccJpyf1nwIVQFGmypgzhH2XdSIBJ4sKiSH3ckzw==',
36+
simple: 'context'
37+
}
38+
}
39+
40+
export function decryptKeyring () {
41+
class TestKeyring extends KeyringNode {
42+
async _onEncrypt () : Promise<NodeEncryptionMaterial> {
43+
throw new Error('I should never see this error')
44+
}
45+
async _onDecrypt (material: NodeDecryptionMaterial) {
46+
const unencryptedDataKey = new Uint8Array(material.suite.keyLengthBytes).fill(0)
47+
const trace = { keyNamespace: 'k', keyName: 'k', flags: KeyringTraceFlag.WRAPPING_KEY_DECRYPTED_DATA_KEY }
48+
return material.setUnencryptedDataKey(unencryptedDataKey, trace)
49+
}
50+
}
51+
52+
return new TestKeyring()
53+
}

modules/encrypt-node/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
},
2525
"devDependencies": {
2626
"@types/chai": "^4.1.4",
27+
"@types/from2": "^2.3.0",
2728
"@types/mocha": "^5.2.5",
2829
"@types/node": "^11.11.4",
2930
"@typescript-eslint/eslint-plugin": "^1.9.0",
3031
"@typescript-eslint/parser": "^1.9.0",
3132
"chai": "^4.1.2",
33+
"from2": "^2.3.0",
3234
"mocha": "^5.2.0",
3335
"nyc": "^14.0.0",
3436
"standard": "^12.0.1",

modules/encrypt-node/src/encrypt.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface EncryptOutput {
2323

2424
export async function encrypt (
2525
cmm: KeyringNode|NodeMaterialsManager,
26-
plaintext: Buffer|Uint8Array|Readable|string,
26+
plaintext: Buffer|Uint8Array|Readable|string|NodeJS.ReadableStream,
2727
op: EncryptInput = {}
2828
): Promise<EncryptOutput> {
2929
const stream = encryptStream(cmm, op)

0 commit comments

Comments
 (0)