mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-10 08:06:43 +00:00
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.
This commit is contained in:
parent
a57bffc84a
commit
e58c02d5ee
@ -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);
|
||||
|
@ -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 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user