Skip to content

Commit 5af7a18

Browse files
authored
Merge branch 'master' into fixed_lerna_versioning
2 parents 0dff9ec + f177cc9 commit 5af7a18

File tree

2 files changed

+108
-26
lines changed

2 files changed

+108
-26
lines changed

modules/decrypt-node/src/verify_stream.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export class VerifyStream extends PortableTransformWithType {
163163
if (this._verify) {
164164
this._verify.update(frameBuffer.slice(0, frameHeader.readPos))
165165
}
166-
const tail = chunk.slice(frameHeader.readPos)
166+
const tail = frameBuffer.slice(frameHeader.readPos)
167167
this.emit('BodyInfo', frameHeader)
168168
state.currentFrame = frameHeader
169169
return setImmediate(() => this._transform(tail, enc, callback))
@@ -219,7 +219,7 @@ export class VerifyStream extends PortableTransformWithType {
219219
*/
220220
this._verifyState.finalAuthTagReceived = true
221221
/* Overwriting the _transform function.
222-
* Data flow control is not handled here.
222+
* Data flow control is now handled here.
223223
*/
224224
this._transform = (
225225
chunk: Buffer,

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

Lines changed: 106 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -59,33 +59,86 @@ describe('decrypt', () => {
5959
fixtures.base64CiphertextAlgAes256GcmIv12Tag16HkdfSha384EcdsaP384(),
6060
'base64'
6161
)
62-
const i = ciphertext.values()
63-
const ciphertextStream = from(
64-
(
65-
_: number,
66-
next: (err: Error | null, chunk: Uint8Array | null) => void
67-
) => {
68-
/* Pushing 1 byte at time is the most annoying thing.
69-
* This is done intentionally to hit _every_ boundary condition.
70-
*/
71-
const { value, done } = i.next()
72-
if (done) return next(null, null)
73-
next(null, new Uint8Array([value]))
74-
}
75-
)
7662

77-
const { plaintext: test, messageHeader } = await decrypt(
78-
fixtures.decryptKeyring(),
79-
ciphertextStream
63+
/* Pushing 1 byte at time is annoying.
64+
* But there can be state that can get reset
65+
* from a 1 byte push when the buffering
66+
* is internal to the stream.
67+
* So while 1 byte will hit the beginning
68+
* boundary conditions,
69+
* and the exiting boundary conditions,
70+
* but it will never span any boundary conditions.
71+
* The prime example of this is
72+
* the `VerifyStream`.
73+
* It has 4 byte boundary in the `_transform` function.
74+
* The body header, the body, the auth tag, and signature.
75+
* By sending 1 byte
76+
* as the code transitions from the body header
77+
* to the body
78+
* the code handling the exiting
79+
* of the body header has no way to interact
80+
* with the body,
81+
* because the 1 byte of data
82+
* completely isolates these code branches.
83+
* So I push at different sizes to hit
84+
* as many boundary conditions as possible.
85+
* Why 20 as a max?
86+
* Permuting every possible combination
87+
* would take too long
88+
* and so anything short of _everything_
89+
* is a compromise.
90+
* 20 seems large enough.
91+
* I did not do an initial offset
92+
* because that just moves me closer
93+
* to permuting every option.
94+
* An alternative would be a variable chunk size.
95+
* Doing this randomly is a bad idea.
96+
* Tests that only fail _sometimes_
97+
* are bad tests.
98+
* It is too easy to try again,
99+
* and when everything passes just move on.
100+
* And again, doing every permutation
101+
* is too expensive at this time.
102+
*/
103+
const results = await Promise.all(
104+
[
105+
{ size: 1 },
106+
{ size: 2 },
107+
{ size: 3 },
108+
{ size: 4 },
109+
{ size: 5 },
110+
{ size: 6 },
111+
{ size: 7 },
112+
{ size: 8 },
113+
{ size: 9 },
114+
{ size: 10 },
115+
{ size: 11 },
116+
{ size: 12 },
117+
{ size: 13 },
118+
{ size: 14 },
119+
{ size: 15 },
120+
{ size: 16 },
121+
{ size: 17 },
122+
{ size: 18 },
123+
{ size: 19 },
124+
{ size: 20 },
125+
].map(async (op) =>
126+
decrypt(
127+
fixtures.decryptKeyring(),
128+
chunkCipherTextStream(ciphertext, op)
129+
)
130+
)
80131
)
81132

82-
expect(messageHeader.suiteId).to.equal(
83-
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384
84-
)
85-
expect(messageHeader.encryptionContext).to.deep.equal(
86-
fixtures.encryptionContext()
87-
)
88-
expect(test.toString('base64')).to.equal(fixtures.base64Plaintext())
133+
results.map(({ plaintext: test, messageHeader }) => {
134+
expect(messageHeader.suiteId).to.equal(
135+
AlgorithmSuiteIdentifier.ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384
136+
)
137+
expect(messageHeader.encryptionContext).to.deep.equal(
138+
fixtures.encryptionContext()
139+
)
140+
expect(test.toString('base64')).to.equal(fixtures.base64Plaintext())
141+
})
89142
})
90143

91144
it('Precondition: The sequence number is required to monotonically increase, starting from 1.', async () => {
@@ -125,3 +178,32 @@ describe('decrypt', () => {
125178
).to.rejectedWith(Error, 'maxBodySize exceeded.')
126179
})
127180
})
181+
182+
function chunkCipherTextStream(ciphertext: Buffer, { size }: { size: number }) {
183+
const i = ciphertext.values()
184+
185+
return from(
186+
(
187+
_: number,
188+
next: (err: Error | null, chunk: Uint8Array | null) => void
189+
) => {
190+
const { value, done } = eat(i, size)
191+
if (done) return next(null, null)
192+
next(null, new Uint8Array(value))
193+
}
194+
)
195+
196+
function eat(i: IterableIterator<number>, size: number) {
197+
return Array(size)
198+
.fill(1)
199+
.reduce<IteratorResult<number[], any>>(
200+
({ value }) => {
201+
const { value: item, done } = i.next()
202+
if (done && value.length) return { value, done: false }
203+
if (!done) value.push(item)
204+
return { value, done }
205+
},
206+
{ value: <number[]>[], done: false }
207+
)
208+
}
209+
}

0 commit comments

Comments
 (0)