From e58c02d5ee0be727996b2728dd2274ce3e1ddb6c Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:23:17 +0200 Subject: [PATCH] Check session key size on SEIPD decryption This is especially important for SEIPDv2 session keys, as a key derivation step is run where the resulting key will always match the expected cipher size, but we want to ensure that the input key isn't e.g. too short. --- .../sym_encrypted_integrity_protected_data.js | 25 +- test/general/packet.js | 610 +++++++----------- 2 files changed, 244 insertions(+), 391 deletions(-) diff --git a/src/packet/sym_encrypted_integrity_protected_data.js b/src/packet/sym_encrypted_integrity_protected_data.js index 4512aaa2..c2762c83 100644 --- a/src/packet/sym_encrypted_integrity_protected_data.js +++ b/src/packet/sym_encrypted_integrity_protected_data.js @@ -128,6 +128,16 @@ class SymEncryptedIntegrityProtectedDataPacket { * @async */ async encrypt(sessionKeyAlgorithm, key, config = defaultConfig) { + // We check that the session key size matches the one expected by the symmetric algorithm. + // This is especially important for SEIPDv2 session keys, as a key derivation step is run where the resulting key will always match the expected cipher size, + // but we want to ensure that the input key isn't e.g. too short. + // The check is done here, instead of on encrypted session key (ESK) encryption, because v6 ESK packets do not store the session key algorithm, + // which is instead included in the SEIPDv2 data. + const { blockSize, keySize } = crypto.getCipherParams(sessionKeyAlgorithm); + if (key.length !== keySize) { + throw new Error('Unexpected session key size'); + } + let bytes = this.packets.write(); if (stream.isArrayStream(bytes)) bytes = await stream.readToEnd(bytes); @@ -138,8 +148,6 @@ class SymEncryptedIntegrityProtectedDataPacket { this.chunkSizeByte = config.aeadChunkSizeByte; this.encrypted = await runAEAD(this, 'encrypt', key, bytes); } else { - const { blockSize } = crypto.getCipherParams(sessionKeyAlgorithm); - const prefix = await crypto.getPrefixRandom(sessionKeyAlgorithm); const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet @@ -162,11 +170,24 @@ class SymEncryptedIntegrityProtectedDataPacket { * @async */ async decrypt(sessionKeyAlgorithm, key, config = defaultConfig) { + // We check that the session key size matches the one expected by the symmetric algorithm. + // This is especially important for SEIPDv2 session keys, as a key derivation step is run where the resulting key will always match the expected cipher size, + // but we want to ensure that the input key isn't e.g. too short. + // The check is done here, instead of on encrypted session key (ESK) decryption, because v6 ESK packets do not store the session key algorithm, + // which is instead included in the SEIPDv2 data. + if (key.length !== crypto.getCipherParams(sessionKeyAlgorithm).keySize) { + throw new Error('Unexpected session key size'); + } + let encrypted = stream.clone(this.encrypted); if (stream.isArrayStream(encrypted)) encrypted = await stream.readToEnd(encrypted); let packetbytes; if (this.version === 2) { + if (this.cipherAlgorithm !== sessionKeyAlgorithm) { + // sanity check + throw new Error('Unexpected session key algorithm'); + } packetbytes = await runAEAD(this, 'decrypt', key, encrypted); } else { const { blockSize } = crypto.getCipherParams(sessionKeyAlgorithm); diff --git a/test/general/packet.js b/test/general/packet.js index 78f5d621..4f8a58d0 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -9,7 +9,7 @@ import openpgp from '../initOpenpgp.js'; import crypto from '../../src/crypto'; import util from '../../src/util.js'; import * as packet from '../../src/packet'; - +import * as random from '../../src/crypto/random'; import * as input from './testInputs.js'; @@ -155,6 +155,50 @@ export default () => describe('Packet', function() { expect(await stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data)); }); + describe('Sym. encrypted integrity protected packet - throw on unexpected session key size', async () => { + async function testSEIPD(packetOptions) { + const symmetricAlgo = openpgp.enums.symmetric.aes256; + const key = random.getRandomBytes(crypto.getCipherParams(symmetricAlgo).keySize); + const unexpectedSymmetricAlgo = openpgp.enums.symmetric.aes128; + const keyWithUnexpectedSize = random.getRandomBytes(crypto.getCipherParams(unexpectedSymmetricAlgo).keySize); + + const plaintext = 'hello world'; + const literal = new openpgp.LiteralDataPacket(); + literal.setText(plaintext); + const seipd = openpgp.SymEncryptedIntegrityProtectedDataPacket.fromObject(packetOptions); + seipd.packets = new openpgp.PacketList(); + seipd.packets.push(literal); + + await expect( + seipd.encrypt(symmetricAlgo, keyWithUnexpectedSize, undefined, openpgp.config)).to.be.rejectedWith(/Unexpected session key size/); + expect(seipd.encrypted).to.be.null; + await seipd.encrypt(symmetricAlgo, key, undefined, openpgp.config); + expect(seipd.encrypted).to.not.be.null; + + const seipdParsed = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); + await seipdParsed.read(seipd.write()); + await expect( + seipdParsed.decrypt(unexpectedSymmetricAlgo, keyWithUnexpectedSize, undefined, openpgp.config) + ).to.be.rejectedWith( + seipdParsed.version === 1 ? + /Modification detected/ : // SEIPDv1 does not store info about the symmetric algo + /Unexpected session key algo/ + ); + await expect( + seipdParsed.decrypt(unexpectedSymmetricAlgo, key, undefined, openpgp.config) + ).to.be.rejectedWith(/Unexpected session key size/); + await expect( + seipdParsed.decrypt(symmetricAlgo, keyWithUnexpectedSize, undefined, openpgp.config) + ).to.be.rejectedWith(/Unexpected session key size/); + await seipdParsed.decrypt(symmetricAlgo, key, undefined, openpgp.config); + + expect(seipdParsed.packets[0].getText()).to.equal(plaintext); + } + + it('SEIPDv1', () => testSEIPD({ version: 1 })); + it('SEIPDv2', () => testSEIPD({ version: 2, aeadAlgorithm: openpgp.enums.aead.gcm })); + }); + it('Sym. encrypted AEAD protected packet (AEADP)', function() { const aeadProtectVal = openpgp.config.aeadProtect; openpgp.config.aeadProtect = false; @@ -284,166 +328,101 @@ export default () => describe('Packet', function() { } }); - it('Sym. encrypted AEAD protected packet test vector (SEIPDv2 - EAX)', async function() { - // From https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#appendix-A-5 - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; + describe('Sym. encrypted integrity protected packet reading/writing test vector (SEIPDv2)', async function () { + const testVectors = [{ + // from https://datatracker.ietf.org/doc/html/rfc9580#appendix-A.9 + algoLabel: 'EAX', + aeadAlgo: openpgp.enums.aead.eax, + padding: util.hexToUint8Array('ae 5b f0 cd 67 05 50 03 55 81 6c b0 c8 ff'.replace(/\s+/g, '')), + sessionKey: util.hexToUint8Array('38 81 ba fe 98 54 12 45 9b 86 c3 6f 98 cb 9a 5e'.replace(/\s+/g, '')), + salt: util.hexToUint8Array('9f f9 0e 3b 32 19 64 f3 a4 29 13 c8 dc c6 61 93 25 01 52 27 ef b7 ea ea a4 9f 04 c2 e6 74 17 5d'.replace(/\s+/g, '')), + encryptedPacketBytes: util.hexToUint8Array(` + d2 69 02 07 01 06 + 9f f9 0e 3b 32 19 64 f3 a4 29 13 c8 dc c6 61 93 + 25 01 52 27 ef b7 ea ea a4 9f 04 c2 e6 74 17 5d + 4a 3d 22 6e d6 af cb 9c a9 ac 12 2c 14 70 e1 1c + 63 d4 c0 ab 24 1c 6a 93 8a d4 8b f9 9a 5a 99 b9 + 0b ba 83 25 de + 61 04 75 40 25 8a b7 95 9a 95 ad 05 1d da 96 eb + 15 43 1d fe f5 f5 e2 25 5c a7 82 61 54 6e 33 9a + `.replace(/\s+/g, '')) + }, + { + // from https://datatracker.ietf.org/doc/html/rfc9580#appendix-A.10 + algoLabel: 'OCB', + aeadAlgo: openpgp.enums.aead.ocb, + padding: util.hexToUint8Array('ae 6a a1 64 9b 56 aa 83 5b 26 13 90 2b d2'.replace(/\s+/g, '')), + sessionKey: util.hexToUint8Array('28 e7 9a b8 23 97 d3 c6 3d e2 4a c2 17 d7 b7 91'.replace(/\s+/g, '')), + salt: util.hexToUint8Array('20 a6 61 f7 31 fc 9a 30 32 b5 62 33 26 02 7e 3a 5d 8d b5 74 8e be ff 0b 0c 59 10 d0 9e cd d6 41'.replace(/\s+/g, '')), + encryptedPacketBytes: util.hexToUint8Array(` + d2 69 02 07 02 06 + 20 a6 61 f7 31 fc 9a 30 32 b5 62 33 26 02 7e 3a + 5d 8d b5 74 8e be ff 0b 0c 59 10 d0 9e cd d6 41 + ff 9f d3 85 62 75 80 35 bc 49 75 4c e1 bf 3f ff + a7 da d0 a3 b8 10 4f 51 33 cf 42 a4 10 0a 83 ee + f4 ca 1b 48 01 + a8 84 6b f4 2b cd a7 c8 ce 9d 65 e2 12 f3 01 cb + cd 98 fd ca de 69 4a 87 7a d4 24 73 23 f6 e8 57 + `.replace(/\s+/g, '')) + }, + { + // from https://datatracker.ietf.org/doc/html/rfc9580#appendix-A.11 + algoLabel: 'GCM', + aeadAlgo: openpgp.enums.aead.gcm, + padding: util.hexToUint8Array('1c e2 26 9a 9e dd ef 81 03 21 72 b7 ed 7c'.replace(/\s+/g, '')), + sessionKey: util.hexToUint8Array('19 36 fc 85 68 98 02 74 bb 90 0d 83 19 36 0c 77'.replace(/\s+/g, '')), + salt: util.hexToUint8Array('fc b9 44 90 bc b9 8b bd c9 d1 06 c6 09 02 66 94 0f 72 e8 9e dc 21 b5 59 6b 15 76 b1 01 ed 0f 9f'.replace(/\s+/g, '')), + encryptedPacketBytes: util.hexToUint8Array(` + d2 69 02 07 03 06 + fc b9 44 90 bc b9 8b bd c9 d1 06 c6 09 02 66 94 + 0f 72 e8 9e dc 21 b5 59 6b 15 76 b1 01 ed 0f 9f + fc 6f c6 d6 5b bf d2 4d cd 07 90 96 6e 6d 1e 85 + a3 00 53 78 4c b1 d8 b6 a0 69 9e f1 21 55 a7 b2 + ad 62 58 53 1b + 57 65 1f d7 77 79 12 fa 95 e3 5d 9b 40 21 6f 69 + a4 c2 48 db 28 ff 43 31 f1 63 29 07 39 9e 6f f9 + `.replace(/\s+/g, '')) + }]; - const packetBytes = util.hexToUint8Array(` - d2 69 02 07 01 06 - 9f f9 0e 3b 32 19 64 f3 a4 29 13 c8 dc c6 61 93 - 25 01 52 27 ef b7 ea ea a4 9f 04 c2 e6 74 17 5d - 4a 3d 22 6e d6 af cb 9c a9 ac 12 2c 14 70 e1 1c - 63 d4 c0 ab 24 1c 6a 93 8a d4 8b f9 9a 5a 99 b9 - 0b ba 83 25 de - 61 04 75 40 25 8a b7 95 9a 95 ad 05 1d da 96 eb - 15 43 1d fe f5 f5 e2 25 5c a7 82 61 54 6e 33 9a - `.replace(/\s+/g, '')); + testVectors.forEach(testVector => it(testVector.algoLabel, async function () { + const nodeCrypto = util.getNodeCrypto(); + if (!nodeCrypto) return; - const padding = util.hexToUint8Array('ae 5b f0 cd 67 05 50 03 55 81 6c b0 c8 ff'.replace(/\s+/g, '')); - const salt = util.hexToUint8Array('9f f9 0e 3b 32 19 64 f3 a4 29 13 c8 dc c6 61 93 25 01 52 27 ef b7 ea ea a4 9f 04 c2 e6 74 17 5d'.replace(/\s+/g, '')); - const key = util.hexToUint8Array('38 81 ba fe 98 54 12 45 9b 86 c3 6f 98 cb 9a 5e'.replace(/\s+/g, '')); - const algo = openpgp.enums.symmetric.aes128; + const symmetricAlgo = openpgp.enums.symmetric.aes128; - const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); - randomBytesStub.withArgs(14).returns(padding); - randomBytesStub.withArgs(32).returns(salt); + const { aeadAlgo: expectedAEADAlgo, padding, salt, sessionKey, encryptedPacketBytes: expectedEncryptedPacketBytes } = testVector; - const literal = new openpgp.LiteralDataPacket(0); - literal.setBytes(util.stringToUint8Array('Hello, world!'), openpgp.enums.literal.binary); - literal.filename = ''; - const pad = new openpgp.PaddingPacket(); - await pad.createPadding(14); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - enc.version = 2; - enc.aeadAlgorithm = openpgp.enums.aead.eax; - enc.packets = new openpgp.PacketList(); - enc.packets.push(literal); - enc.packets.push(pad); - const msg = new openpgp.PacketList(); - msg.push(enc); + const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); + randomBytesStub.withArgs(14).returns(padding); + randomBytesStub.withArgs(32).returns(salt); - const msg2 = new openpgp.PacketList(); + const literal = new openpgp.LiteralDataPacket(0); + literal.setBytes(util.stringToUint8Array('Hello, world!'), openpgp.enums.literal.binary); + literal.filename = ''; + const pad = new openpgp.PaddingPacket(); + await pad.createPadding(14); + const seipdv2 = openpgp.SymEncryptedIntegrityProtectedDataPacket.fromObject({ version: 2, aeadAlgorithm: expectedAEADAlgo }); + seipdv2.packets = new openpgp.PacketList(); + seipdv2.packets.push(literal); + seipdv2.packets.push(pad); + const msg = new openpgp.PacketList(); + msg.push(seipdv2); - try { - await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 6 }); - const data = msg.write(); - expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); - await msg2.read(data, allAllowedPackets); - await msg2[0].decrypt(algo, key); - expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); - } finally { - randomBytesStub.restore(); - } - }); + try { + await seipdv2.encrypt(symmetricAlgo, sessionKey, { ...openpgp.config, aeadChunkSizeByte: 6 }); + const data = msg.write(); + expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(expectedEncryptedPacketBytes); - it('Sym. encrypted AEAD protected packet test vector (SEIPDv2 - OCB)', async function() { - // From https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#appendix-A-5 - - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; - - const packetBytes = util.hexToUint8Array(` - d2 69 02 07 02 06 - 20 a6 61 f7 31 fc 9a 30 32 b5 62 33 26 02 7e 3a - 5d 8d b5 74 8e be ff 0b 0c 59 10 d0 9e cd d6 41 - ff 9f d3 85 62 75 80 35 bc 49 75 4c e1 bf 3f ff - a7 da d0 a3 b8 10 4f 51 33 cf 42 a4 10 0a 83 ee - f4 ca 1b 48 01 - a8 84 6b f4 2b cd a7 c8 ce 9d 65 e2 12 f3 01 cb - cd 98 fd ca de 69 4a 87 7a d4 24 73 23 f6 e8 57 - `.replace(/\s+/g, '')); - - const padding = util.hexToUint8Array('ae 6a a1 64 9b 56 aa 83 5b 26 13 90 2b d2'.replace(/\s+/g, '')); - const salt = util.hexToUint8Array('20 a6 61 f7 31 fc 9a 30 32 b5 62 33 26 02 7e 3a 5d 8d b5 74 8e be ff 0b 0c 59 10 d0 9e cd d6 41'.replace(/\s+/g, '')); - const key = util.hexToUint8Array('28 e7 9a b8 23 97 d3 c6 3d e2 4a c2 17 d7 b7 91'.replace(/\s+/g, '')); - const algo = openpgp.enums.symmetric.aes128; - - const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); - randomBytesStub.withArgs(14).returns(padding); - randomBytesStub.withArgs(32).returns(salt); - - const literal = new openpgp.LiteralDataPacket(0); - literal.setBytes(util.stringToUint8Array('Hello, world!'), openpgp.enums.literal.binary); - literal.filename = ''; - const pad = new openpgp.PaddingPacket(); - await pad.createPadding(14); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - enc.version = 2; - enc.aeadAlgorithm = openpgp.enums.aead.ocb; - enc.packets = new openpgp.PacketList(); - enc.packets.push(literal); - enc.packets.push(pad); - const msg = new openpgp.PacketList(); - msg.push(enc); - - const msg2 = new openpgp.PacketList(); - - try { - await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 6 }); - const data = msg.write(); - expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); - await msg2.read(data, allAllowedPackets); - await msg2[0].decrypt(algo, key); - expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); - } finally { - randomBytesStub.restore(); - } - }); - - it('Sym. encrypted AEAD protected packet test vector (SEIPDv2 - GCM)', async function() { - // From https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#appendix-A-5 - - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; - - const packetBytes = util.hexToUint8Array(` - d2 69 02 07 03 06 - fc b9 44 90 bc b9 8b bd c9 d1 06 c6 09 02 66 94 - 0f 72 e8 9e dc 21 b5 59 6b 15 76 b1 01 ed 0f 9f - fc 6f c6 d6 5b bf d2 4d cd 07 90 96 6e 6d 1e 85 - a3 00 53 78 4c b1 d8 b6 a0 69 9e f1 21 55 a7 b2 - ad 62 58 53 1b - 57 65 1f d7 77 79 12 fa 95 e3 5d 9b 40 21 6f 69 - a4 c2 48 db 28 ff 43 31 f1 63 29 07 39 9e 6f f9 - `.replace(/\s+/g, '')); - - const padding = util.hexToUint8Array('1c e2 26 9a 9e dd ef 81 03 21 72 b7 ed 7c'.replace(/\s+/g, '')); - const salt = util.hexToUint8Array('fc b9 44 90 bc b9 8b bd c9 d1 06 c6 09 02 66 94 0f 72 e8 9e dc 21 b5 59 6b 15 76 b1 01 ed 0f 9f'.replace(/\s+/g, '')); - const key = util.hexToUint8Array('19 36 fc 85 68 98 02 74 bb 90 0d 83 19 36 0c 77'.replace(/\s+/g, '')); - const algo = openpgp.enums.symmetric.aes128; - - const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); - randomBytesStub.withArgs(14).returns(padding); - randomBytesStub.withArgs(32).returns(salt); - - const literal = new openpgp.LiteralDataPacket(0); - literal.setBytes(util.stringToUint8Array('Hello, world!'), openpgp.enums.literal.binary); - literal.filename = ''; - const pad = new openpgp.PaddingPacket(); - await pad.createPadding(14); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - enc.version = 2; - enc.aeadAlgorithm = openpgp.enums.aead.gcm; - enc.packets = new openpgp.PacketList(); - enc.packets.push(literal); - enc.packets.push(pad); - const msg = new openpgp.PacketList(); - msg.push(enc); - - const msg2 = new openpgp.PacketList(); - - try { - await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 6 }); - const data = msg.write(); - expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); - await msg2.read(data, allAllowedPackets); - await msg2[0].decrypt(algo, key); - expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); - } finally { - randomBytesStub.restore(); - } + const msg2 = new openpgp.PacketList(); + await msg2.read(data, allAllowedPackets); + expect(msg2[0].cipherAlgorithm).to.equal(openpgp.enums.symmetric.aes128); + await msg2[0].decrypt(symmetricAlgo, sessionKey); + expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); + } finally { + randomBytesStub.restore(); + } + })); }); it('Sym. encrypted session key with a compressed packet', async function() { @@ -914,245 +893,98 @@ export default () => describe('Packet', function() { } }); - it('Sym. encrypted session key reading/writing test vector (SEIPDv2, EAX)', async function() { - // From https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#appendix-A.5 + describe('Sym. encrypted session key reading/writing test vector (SKESK v6)', async function () { + const testVectors = [{ + // from https://datatracker.ietf.org/doc/html/rfc9580#appendix-A.9.1 + algoLabel: 'EAX', + aeadAlgo: openpgp.enums.aead.eax, + s2kSalt: util.hexToUint8Array('a5 ae 57 9d 1f c5 d8 2b'.replace(/\s+/g, '')), + sessionKey: util.hexToUint8Array('38 81 ba fe 98 54 12 45 9b 86 c3 6f 98 cb 9a 5e'.replace(/\s+/g, '')), + sessionIV: util.hexToUint8Array('69 22 4f 91 99 93 b3 50 6f a3 b5 9a 6a 73 cf f8'.replace(/\s+/g, '')), + encryptedPacketBytes: util.hexToUint8Array(` + c3 40 06 1e 07 01 0b 03 + 08 a5 ae 57 9d 1f c5 d8 + 2b ff 69 22 4f 91 99 93 + b3 50 6f a3 b5 9a 6a 73 + cf f8 c5 ef c5 f4 1c 57 + fb 54 e1 c2 26 81 5d 78 + 28 f5 f9 2c 45 4e b6 5e + be 00 ab 59 86 c6 8e 6e + 7c 55`.replace(/\s+/g, '')) + }, + { + algoLabel: 'OCB', + aeadAlgo: openpgp.enums.aead.ocb, + padding: util.hexToUint8Array('ae 6a a1 64 9b 56 aa 83 5b 26 13 90 2b d2'.replace(/\s+/g, '')), + s2kSalt: util.hexToUint8Array('56 a2 98 d2 f5 e3 64 53'.replace(/\s+/g, '')), + sessionKey: util.hexToUint8Array('28 e7 9a b8 23 97 d3 c6 3d e2 4a c2 17 d7 b7 91'.replace(/\s+/g, '')), + sessionIV: util.hexToUint8Array('cf cc 5c 11 66 4e db 9d b4 25 90 d7 dc 46 b0'.replace(/\s+/g, '')), + encryptedPacketBytes: util.hexToUint8Array(` + c3 3f 06 1d 07 02 0b 03 + 08 56 a2 98 d2 f5 e3 64 + 53 ff cf cc 5c 11 66 4e + db 9d b4 25 90 d7 dc 46 + b0 72 41 b6 12 c3 81 2c + ff fb ea 00 f2 34 7b 25 + 64 11 23 f8 87 ae 60 d4 + fd 61 4e 08 37 d8 19 d3 + 6c`.replace(/\s+/g, '')) + }, + { + algoLabel: 'GCM', + aeadAlgo: openpgp.enums.aead.gcm, + s2kSalt: util.hexToUint8Array('e9 d3 97 85 b2 07 00 08'.replace(/\s+/g, '')), + sessionKey: util.hexToUint8Array('19 36 fc 85 68 98 02 74 bb 90 0d 83 19 36 0c 77'.replace(/\s+/g, '')), + sessionIV: util.hexToUint8Array('b4 2e 7c 48 3e f4 88 44 57 cb 37 26'.replace(/\s+/g, '')), + encryptedPacketBytes: util.hexToUint8Array(` + c3 3c 06 1a 07 03 0b 03 + 08 e9 d3 97 85 b2 07 00 + 08 ff b4 2e 7c 48 3e f4 + 88 44 57 cb 37 26 b9 b3 + db 9f f7 76 e5 f4 d9 a4 + 09 52 e2 44 72 98 85 1a + bf ff 75 26 df 2d d5 54 + 41 75 79 a7 79 9f`.replace(/\s+/g, '')) + }]; - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; + testVectors.forEach(testVector => it(testVector.algoLabel, async function () { + const nodeCrypto = util.getNodeCrypto(); - const aeadProtectVal = openpgp.config.aeadProtect; - const aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - const s2kIterationCountByteVal = openpgp.config.s2kIterationCountByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 6; - openpgp.config.s2kIterationCountByte = 255; + const { aeadAlgo: expectedAEADAlgo, s2kSalt, sessionKey, sessionIV, encryptedPacketBytes: expectedEncryptedPacketBytes } = testVector; + const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); + randomBytesStub.onCall(0).returns(s2kSalt); + randomBytesStub.onCall(1).returns(sessionKey); + randomBytesStub.onCall(2).returns(sessionIV); - const padding = util.hexToUint8Array('ae 5b f0 cd 67 05 50 03 55 81 6c b0 c8 ff'.replace(/\s+/g, '')); - const salt = util.hexToUint8Array('a5 ae 57 9d 1f c5 d8 2b'.replace(/\s+/g, '')); - const sessionKey = util.hexToUint8Array('38 81 ba fe 98 54 12 45 9b 86 c3 6f 98 cb 9a 5e'.replace(/\s+/g, '')); - const sessionIV = util.hexToUint8Array('69 22 4f 91 99 93 b3 50 6f a3 b5 9a 6a 73 cf f8'.replace(/\s+/g, '')); - const dataSalt = util.hexToUint8Array('9f f9 0e 3b 32 19 64 f3 a4 29 13 c8 dc c6 61 93 25 01 52 27 ef b7 ea ea a4 9f 04 c2 e6 74 17 5d'.replace(/\s+/g, '')); - - const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); - randomBytesStub.onCall(0).returns(padding); - randomBytesStub.onCall(1).returns(salt); - randomBytesStub.onCall(2).returns(sessionKey); - randomBytesStub.onCall(3).returns(sessionIV); - randomBytesStub.onCall(4).returns(dataSalt); - - const { data: packetBytes } = await openpgp.unarmor(`-----BEGIN PGP MESSAGE----- - -w0AGHgcBCwMIpa5XnR/F2Cv/aSJPkZmTs1Bvo7WaanPP+MXvxfQcV/tU4cImgV14 -KPX5LEVOtl6+AKtZhsaObnxV0mkCBwEGn/kOOzIZZPOkKRPI3MZhkyUBUifvt+rq -pJ8EwuZ0F11KPSJu1q/LnKmsEiwUcOEcY9TAqyQcapOK1Iv5mlqZuQu6gyXeYQR1 -QCWKt5Wala0FHdqW6xVDHf719eIlXKeCYVRuM5o= ------END PGP MESSAGE----- -`); - - try { - const passphrase = 'password'; - const algo = openpgp.enums.symmetric.aes128; - - const skesk = new openpgp.SymEncryptedSessionKeyPacket(); - skesk.sessionKeyAlgorithm = algo; - const literal = new openpgp.LiteralDataPacket(0); - literal.setBytes(util.stringToUint8Array('Hello, world!'), openpgp.enums.literal.binary); - literal.filename = ''; - const pad = new openpgp.PaddingPacket(); - await pad.createPadding(14); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - enc.version = 2; - enc.aeadAlgorithm = skesk.aeadAlgorithm = openpgp.enums.aead.eax; - enc.packets = new openpgp.PacketList(); - enc.packets.push(literal); - enc.packets.push(pad); - const msg = new openpgp.PacketList(); - msg.push(skesk); - msg.push(enc); - - await skesk.encrypt(passphrase, openpgp.config); - - const key = skesk.sessionKey; - await enc.encrypt(algo, key, undefined, openpgp.config); - - const data = msg.write(); - expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); - - const msg2 = new openpgp.PacketList(); - await msg2.read(data, allAllowedPackets); - - await msg2[0].decrypt(passphrase); - const key2 = msg2[0].sessionKey; - await msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2); - - expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); - } finally { - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; - openpgp.config.s2kIterationCountByte = s2kIterationCountByteVal; - randomBytesStub.restore(); - } - }); - - it('Sym. encrypted session key reading/writing test vector (SEIPDv2, OCB)', async function() { - // From https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#appendix-A.6 - - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; - - const aeadProtectVal = openpgp.config.aeadProtect; - const aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - const s2kIterationCountByteVal = openpgp.config.s2kIterationCountByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 6; - openpgp.config.s2kIterationCountByte = 255; - - const padding = util.hexToUint8Array('ae 6a a1 64 9b 56 aa 83 5b 26 13 90 2b d2'.replace(/\s+/g, '')); - const salt = util.hexToUint8Array('56 a2 98 d2 f5 e3 64 53'.replace(/\s+/g, '')); - const sessionKey = util.hexToUint8Array('28 e7 9a b8 23 97 d3 c6 3d e2 4a c2 17 d7 b7 91'.replace(/\s+/g, '')); - const sessionIV = util.hexToUint8Array('cf cc 5c 11 66 4e db 9d b4 25 90 d7 dc 46 b0'.replace(/\s+/g, '')); - const dataSalt = util.hexToUint8Array('20 a6 61 f7 31 fc 9a 30 32 b5 62 33 26 02 7e 3a 5d 8d b5 74 8e be ff 0b 0c 59 10 d0 9e cd d6 41'.replace(/\s+/g, '')); - - const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); - randomBytesStub.onCall(0).returns(padding); - randomBytesStub.onCall(1).returns(salt); - randomBytesStub.onCall(2).returns(sessionKey); - randomBytesStub.onCall(3).returns(sessionIV); - randomBytesStub.onCall(4).returns(dataSalt); - - const { data: packetBytes } = await openpgp.unarmor(`-----BEGIN PGP MESSAGE----- - -wz8GHQcCCwMIVqKY0vXjZFP/z8xcEWZO2520JZDX3EawckG2EsOBLP/76gDyNHsl -ZBEj+IeuYNT9YU4IN9gZ02zSaQIHAgYgpmH3MfyaMDK1YjMmAn46XY21dI6+/wsM -WRDQns3WQf+f04VidYA1vEl1TOG/P/+n2tCjuBBPUTPPQqQQCoPu9MobSAGohGv0 -K82nyM6dZeIS8wHLzZj9yt5pSod61CRzI/boVw== ------END PGP MESSAGE----- -`); - - try { - const passphrase = 'password'; - const algo = openpgp.enums.symmetric.aes128; - - const skesk = new openpgp.SymEncryptedSessionKeyPacket(); - skesk.sessionKeyAlgorithm = algo; - const literal = new openpgp.LiteralDataPacket(0); - literal.setBytes(util.stringToUint8Array('Hello, world!'), openpgp.enums.literal.binary); - literal.filename = ''; - const pad = new openpgp.PaddingPacket(); - await pad.createPadding(14); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - enc.version = 2; - enc.aeadAlgorithm = skesk.aeadAlgorithm = openpgp.enums.aead.ocb; - enc.packets = new openpgp.PacketList(); - enc.packets.push(literal); - enc.packets.push(pad); - const msg = new openpgp.PacketList(); - msg.push(skesk); - msg.push(enc); - - await skesk.encrypt(passphrase, openpgp.config); - - const key = skesk.sessionKey; - await enc.encrypt(algo, key, undefined, openpgp.config); - - const data = msg.write(); - expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); - - const msg2 = new openpgp.PacketList(); - await msg2.read(data, allAllowedPackets); - - await msg2[0].decrypt(passphrase); - const key2 = msg2[0].sessionKey; - await msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2); - - expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); - } finally { - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; - openpgp.config.s2kIterationCountByte = s2kIterationCountByteVal; - randomBytesStub.restore(); - } - }); - - it('Sym. encrypted session key reading/writing test vector (SEIPDv2, GCM)', async function() { - // From https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#appendix-A.7 - - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; - - const aeadProtectVal = openpgp.config.aeadProtect; - const aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - const s2kIterationCountByteVal = openpgp.config.s2kIterationCountByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 6; - openpgp.config.s2kIterationCountByte = 255; - - const padding = util.hexToUint8Array('1c e2 26 9a 9e dd ef 81 03 21 72 b7 ed 7c'.replace(/\s+/g, '')); - const salt = util.hexToUint8Array('e9 d3 97 85 b2 07 00 08'.replace(/\s+/g, '')); - const sessionKey = util.hexToUint8Array('19 36 fc 85 68 98 02 74 bb 90 0d 83 19 36 0c 77'.replace(/\s+/g, '')); - const sessionIV = util.hexToUint8Array('b4 2e 7c 48 3e f4 88 44 57 cb 37 26'.replace(/\s+/g, '')); - const dataSalt = util.hexToUint8Array('fc b9 44 90 bc b9 8b bd c9 d1 06 c6 09 02 66 94 0f 72 e8 9e dc 21 b5 59 6b 15 76 b1 01 ed 0f 9f'.replace(/\s+/g, '')); - - const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes'); - randomBytesStub.onCall(0).returns(padding); - randomBytesStub.onCall(1).returns(salt); - randomBytesStub.onCall(2).returns(sessionKey); - randomBytesStub.onCall(3).returns(sessionIV); - randomBytesStub.onCall(4).returns(dataSalt); - - const { data: packetBytes } = await openpgp.unarmor(`-----BEGIN PGP MESSAGE----- - -wzwGGgcDCwMI6dOXhbIHAAj/tC58SD70iERXyzcmubPbn/d25fTZpAlS4kRymIUa -v/91Jt8t1VRBdXmneZ/SaQIHAwb8uUSQvLmLvcnRBsYJAmaUD3LontwhtVlrFXax -Ae0Pn/xvxtZbv9JNzQeQlm5tHoWjAFN4TLHYtqBpnvEhVaeyrWJYUxtXZR/Xd3kS -+pXjXZtAIW9ppMJI2yj/QzHxYykHOZ5v+Q== ------END PGP MESSAGE----- -`); - - try { - const passphrase = 'password'; - const algo = openpgp.enums.symmetric.aes128; - - const skesk = new openpgp.SymEncryptedSessionKeyPacket(); - skesk.sessionKeyAlgorithm = algo; - const literal = new openpgp.LiteralDataPacket(0); - literal.setBytes(util.stringToUint8Array('Hello, world!'), openpgp.enums.literal.binary); - literal.filename = ''; - const pad = new openpgp.PaddingPacket(); - await pad.createPadding(14); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - enc.version = 2; - enc.aeadAlgorithm = skesk.aeadAlgorithm = openpgp.enums.aead.gcm; - enc.packets = new openpgp.PacketList(); - enc.packets.push(literal); - enc.packets.push(pad); - const msg = new openpgp.PacketList(); - msg.push(skesk); - msg.push(enc); - - await skesk.encrypt(passphrase, openpgp.config); - - const key = skesk.sessionKey; - await enc.encrypt(algo, key, undefined, openpgp.config); - - const data = msg.write(); - expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); - - const msg2 = new openpgp.PacketList(); - await msg2.read(data, allAllowedPackets); - - await msg2[0].decrypt(passphrase); - const key2 = msg2[0].sessionKey; - await msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2); - - expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); - } finally { - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; - openpgp.config.s2kIterationCountByte = s2kIterationCountByteVal; - randomBytesStub.restore(); - } - }); + try { + const passphrase = 'password'; + const symmetricAlgo = openpgp.enums.symmetric.aes128; + + const skesk = new openpgp.SymEncryptedSessionKeyPacket(); + skesk.version = 6; + skesk.sessionKeyAlgorithm = symmetricAlgo; + skesk.aeadAlgorithm = expectedAEADAlgo; + const msg = new openpgp.PacketList(); + msg.push(skesk); + + await skesk.encrypt(passphrase, { ...openpgp.config, aeadChunkSizeByte: 6, s2kIterationCountByte: 255 }); + + const serialized = msg.write(); + expect(await stream.readToEnd(stream.clone(serialized))).to.deep.equal(expectedEncryptedPacketBytes); + + const msg2 = new openpgp.PacketList(); + await msg2.read(serialized, allAllowedPackets); + + await msg2[0].decrypt(passphrase); + const decryptedSessionKey = msg2[0].sessionKey; + const decryptedSessionKeyAlgo = msg2[0].sessionKeyAlgorithm; + expect(decryptedSessionKey).to.deep.equal(sessionKey); + expect(decryptedSessionKeyAlgo).to.be.null; + } finally { + randomBytesStub.restore(); + } + })); + }) it('Secret key encryption/decryption test', async function() { const armored_msg =