From 1e5369d9428c7a3b517e03626ae7180beac1408f Mon Sep 17 00:00:00 2001 From: seebees Date: Thu, 10 Mar 2022 12:20:17 -0800 Subject: [PATCH 1/5] 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`. --- modules/encrypt-browser/src/encrypt.ts | 5 ++- modules/encrypt-browser/test/encrypt.test.ts | 41 ++++++++++++++++++++ modules/encrypt-node/test/encrypt.test.ts | 28 +++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/modules/encrypt-browser/src/encrypt.ts b/modules/encrypt-browser/src/encrypt.ts index 85676b7c5..f58b627f6 100644 --- a/modules/encrypt-browser/src/encrypt.ts +++ b/modules/encrypt-browser/src/encrypt.ts @@ -138,14 +138,15 @@ export async function _encrypt( await getSubtleEncrypt(headerIv, header)(new Uint8Array(0)) ) - const numberOfFrames = Math.ceil(plaintextLength / frameLength) + // In the case of plaintextLength == 0 there still needs to be 1 frame. + const numberOfFrames = Math.max(Math.ceil(plaintextLength / frameLength), 1) /* The final frame has a variable length. * The value needs to be known, but should only be calculated once. * So I calculate how much of a frame I should have at the end. * This value will NEVER be larger than the frameLength. */ const finalFrameLength = - frameLength - (numberOfFrames * frameLength - plaintextLength) + plaintextLength - Math.floor(plaintextLength / frameLength) * frameLength const bodyContent = [] for ( diff --git a/modules/encrypt-browser/test/encrypt.test.ts b/modules/encrypt-browser/test/encrypt.test.ts index dd11cebb2..506dfca73 100644 --- a/modules/encrypt-browser/test/encrypt.test.ts +++ b/modules/encrypt-browser/test/encrypt.test.ts @@ -252,4 +252,45 @@ describe('encrypt structural testing', () => { // This will throw if it does not deserialize correctly deserializeSignature(footerSection) }) + + it('can encrypt empty message', async () => { + const encryptionContext = { simple: 'context' } + + const plaintext = new Uint8Array() + const { result, messageHeader } = await encrypt(keyRing, plaintext, { + encryptionContext, + }) + + /* The default algorithm suite will add a signature key to the context. + * So I only check that the passed context elements exist. + */ + expect(messageHeader.encryptionContext) + .to.haveOwnProperty('simple') + .and.to.equal('context') + expect(messageHeader.encryptedDataKeys).lengthOf(1) + expect(messageHeader.encryptedDataKeys[0]).to.deep.equal(edk) + + const headerInfo = deserializeMessageHeader(result) + if (!headerInfo) throw new Error('I should never see this error') + + expect(messageHeader).to.deep.equal(headerInfo.messageHeader) + + const readPos = + headerInfo.headerLength + headerInfo.headerAuth.headerAuthLength + const bodyHeader = decodeBodyHeader(result, headerInfo, readPos) + + if (!bodyHeader) throw new Error('I should never see this error') + + expect(bodyHeader.isFinalFrame).to.equal(true) + expect(bodyHeader.contentLength).to.equal(0) + + const sigPos = + bodyHeader.readPos + bodyHeader.contentLength + bodyHeader.tagLength / 8 + + // This implicitly tests that I have consumed all the data, + // because otherwise the footer section will be too large + const footerSection = result.slice(sigPos) + // This will throw if it does not deserialize correctly + deserializeSignature(footerSection) + }) }) diff --git a/modules/encrypt-node/test/encrypt.test.ts b/modules/encrypt-node/test/encrypt.test.ts index c04e8a910..a289f2367 100644 --- a/modules/encrypt-node/test/encrypt.test.ts +++ b/modules/encrypt-node/test/encrypt.test.ts @@ -370,6 +370,34 @@ describe('encrypt structural testing', () => { // This will throw if it does not deserialize correctly deserializeSignature(footerSection) }) + + it('can encrypt empty message', async () => { + const plaintext = new Uint8Array() + const { result, messageHeader } = await encrypt(keyRing, plaintext) + + const headerInfo = deserializeMessageHeader(result) + if (!headerInfo) throw new Error('this should never happen') + + expect(messageHeader).to.deep.equal(headerInfo.messageHeader) + + const readPos = + headerInfo.headerLength + headerInfo.headerAuth.headerAuthLength + const bodyHeader = decodeBodyHeader(result, headerInfo, readPos) + + if (!bodyHeader) throw new Error('I should never see this error') + + expect(bodyHeader.isFinalFrame).to.equal(true) + expect(bodyHeader.contentLength).to.equal(0) + + const sigPos = + bodyHeader.readPos + bodyHeader.contentLength + bodyHeader.tagLength / 8 + + // This implicitly tests that I have consumed all the data, + // because otherwise the footer section will be too large + const footerSection = result.slice(sigPos) + // This will throw if it does not deserialize correctly + deserializeSignature(footerSection) + }) }) async function finishedAsync(stream: any) { From a8315f4c4cb5a52e60db4570122c1a0320ef1a67 Mon Sep 17 00:00:00 2001 From: seebees Date: Thu, 10 Mar 2022 14:51:36 -0800 Subject: [PATCH 2/5] Update modules/encrypt-browser/src/encrypt.ts Co-authored-by: Alex Chew --- modules/encrypt-browser/src/encrypt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/encrypt-browser/src/encrypt.ts b/modules/encrypt-browser/src/encrypt.ts index f58b627f6..714786ecc 100644 --- a/modules/encrypt-browser/src/encrypt.ts +++ b/modules/encrypt-browser/src/encrypt.ts @@ -146,7 +146,7 @@ export async function _encrypt( * This value will NEVER be larger than the frameLength. */ const finalFrameLength = - plaintextLength - Math.floor(plaintextLength / frameLength) * frameLength + plaintextLength - (numberOfFrames - 1) * frameLength const bodyContent = [] for ( From 041a5cc6690661d05639f79ee071f830f373e9c9 Mon Sep 17 00:00:00 2001 From: seebees Date: Thu, 10 Mar 2022 14:52:34 -0800 Subject: [PATCH 3/5] Node and the browser test should be the same --- modules/encrypt-node/test/encrypt.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/encrypt-node/test/encrypt.test.ts b/modules/encrypt-node/test/encrypt.test.ts index a289f2367..cec2229e2 100644 --- a/modules/encrypt-node/test/encrypt.test.ts +++ b/modules/encrypt-node/test/encrypt.test.ts @@ -375,6 +375,15 @@ describe('encrypt structural testing', () => { const plaintext = new Uint8Array() const { result, messageHeader } = await encrypt(keyRing, plaintext) + /* The default algorithm suite will add a signature key to the context. + * So I only check that the passed context elements exist. + */ + expect(messageHeader.encryptionContext) + .to.haveOwnProperty('simple') + .and.to.equal('context') + expect(messageHeader.encryptedDataKeys).lengthOf(1) + expect(messageHeader.encryptedDataKeys[0]).to.deep.equal(edk) + const headerInfo = deserializeMessageHeader(result) if (!headerInfo) throw new Error('this should never happen') From f0c9af90d7f17ce9cfc3cddf9781519089db541a Mon Sep 17 00:00:00 2001 From: seebees Date: Fri, 11 Mar 2022 09:38:33 -0800 Subject: [PATCH 4/5] Need encryption context --- modules/encrypt-node/test/encrypt.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/encrypt-node/test/encrypt.test.ts b/modules/encrypt-node/test/encrypt.test.ts index cec2229e2..6f38ef27e 100644 --- a/modules/encrypt-node/test/encrypt.test.ts +++ b/modules/encrypt-node/test/encrypt.test.ts @@ -373,7 +373,10 @@ describe('encrypt structural testing', () => { it('can encrypt empty message', async () => { const plaintext = new Uint8Array() - const { result, messageHeader } = await encrypt(keyRing, plaintext) + const encryptionContext = { simple: 'context' } + const { result, messageHeader } = await encrypt(keyRing, plaintext, { + encryptionContext, + }) /* The default algorithm suite will add a signature key to the context. * So I only check that the passed context elements exist. From f6d3fe9f991bd9ff92e92710ae9e365a2dff08aa Mon Sep 17 00:00:00 2001 From: seebees Date: Fri, 11 Mar 2022 09:55:20 -0800 Subject: [PATCH 5/5] lint --- modules/encrypt-browser/src/encrypt.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/encrypt-browser/src/encrypt.ts b/modules/encrypt-browser/src/encrypt.ts index 714786ecc..ccaeae1f3 100644 --- a/modules/encrypt-browser/src/encrypt.ts +++ b/modules/encrypt-browser/src/encrypt.ts @@ -145,8 +145,7 @@ export async function _encrypt( * So I calculate how much of a frame I should have at the end. * This value will NEVER be larger than the frameLength. */ - const finalFrameLength = - plaintextLength - (numberOfFrames - 1) * frameLength + const finalFrameLength = plaintextLength - (numberOfFrames - 1) * frameLength const bodyContent = [] for (