Skip to content

Commit 1e5369d

Browse files
committed
fix: browser-encrypt can encrypt 0 bytes
Browser encryption now correctly encrypts 0 byte messages. Added structural tests for both Node.js and browser `encrypt`.
1 parent d38fe3d commit 1e5369d

File tree

3 files changed

+72
-2
lines changed

3 files changed

+72
-2
lines changed

modules/encrypt-browser/src/encrypt.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,15 @@ export async function _encrypt(
138138
await getSubtleEncrypt(headerIv, header)(new Uint8Array(0))
139139
)
140140

141-
const numberOfFrames = Math.ceil(plaintextLength / frameLength)
141+
// In the case of plaintextLength == 0 there still needs to be 1 frame.
142+
const numberOfFrames = Math.max(Math.ceil(plaintextLength / frameLength), 1)
142143
/* The final frame has a variable length.
143144
* The value needs to be known, but should only be calculated once.
144145
* So I calculate how much of a frame I should have at the end.
145146
* This value will NEVER be larger than the frameLength.
146147
*/
147148
const finalFrameLength =
148-
frameLength - (numberOfFrames * frameLength - plaintextLength)
149+
plaintextLength - Math.floor(plaintextLength / frameLength) * frameLength
149150
const bodyContent = []
150151

151152
for (

modules/encrypt-browser/test/encrypt.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,45 @@ describe('encrypt structural testing', () => {
252252
// This will throw if it does not deserialize correctly
253253
deserializeSignature(footerSection)
254254
})
255+
256+
it('can encrypt empty message', async () => {
257+
const encryptionContext = { simple: 'context' }
258+
259+
const plaintext = new Uint8Array()
260+
const { result, messageHeader } = await encrypt(keyRing, plaintext, {
261+
encryptionContext,
262+
})
263+
264+
/* The default algorithm suite will add a signature key to the context.
265+
* So I only check that the passed context elements exist.
266+
*/
267+
expect(messageHeader.encryptionContext)
268+
.to.haveOwnProperty('simple')
269+
.and.to.equal('context')
270+
expect(messageHeader.encryptedDataKeys).lengthOf(1)
271+
expect(messageHeader.encryptedDataKeys[0]).to.deep.equal(edk)
272+
273+
const headerInfo = deserializeMessageHeader(result)
274+
if (!headerInfo) throw new Error('I should never see this error')
275+
276+
expect(messageHeader).to.deep.equal(headerInfo.messageHeader)
277+
278+
const readPos =
279+
headerInfo.headerLength + headerInfo.headerAuth.headerAuthLength
280+
const bodyHeader = decodeBodyHeader(result, headerInfo, readPos)
281+
282+
if (!bodyHeader) throw new Error('I should never see this error')
283+
284+
expect(bodyHeader.isFinalFrame).to.equal(true)
285+
expect(bodyHeader.contentLength).to.equal(0)
286+
287+
const sigPos =
288+
bodyHeader.readPos + bodyHeader.contentLength + bodyHeader.tagLength / 8
289+
290+
// This implicitly tests that I have consumed all the data,
291+
// because otherwise the footer section will be too large
292+
const footerSection = result.slice(sigPos)
293+
// This will throw if it does not deserialize correctly
294+
deserializeSignature(footerSection)
295+
})
255296
})

modules/encrypt-node/test/encrypt.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,34 @@ describe('encrypt structural testing', () => {
370370
// This will throw if it does not deserialize correctly
371371
deserializeSignature(footerSection)
372372
})
373+
374+
it('can encrypt empty message', async () => {
375+
const plaintext = new Uint8Array()
376+
const { result, messageHeader } = await encrypt(keyRing, plaintext)
377+
378+
const headerInfo = deserializeMessageHeader(result)
379+
if (!headerInfo) throw new Error('this should never happen')
380+
381+
expect(messageHeader).to.deep.equal(headerInfo.messageHeader)
382+
383+
const readPos =
384+
headerInfo.headerLength + headerInfo.headerAuth.headerAuthLength
385+
const bodyHeader = decodeBodyHeader(result, headerInfo, readPos)
386+
387+
if (!bodyHeader) throw new Error('I should never see this error')
388+
389+
expect(bodyHeader.isFinalFrame).to.equal(true)
390+
expect(bodyHeader.contentLength).to.equal(0)
391+
392+
const sigPos =
393+
bodyHeader.readPos + bodyHeader.contentLength + bodyHeader.tagLength / 8
394+
395+
// This implicitly tests that I have consumed all the data,
396+
// because otherwise the footer section will be too large
397+
const footerSection = result.slice(sigPos)
398+
// This will throw if it does not deserialize correctly
399+
deserializeSignature(footerSection)
400+
})
373401
})
374402

375403
async function finishedAsync(stream: any) {

0 commit comments

Comments
 (0)