Tests: add support for RNG mocking in browser tests

The affected tests were previously only run in Node.
This commit is contained in:
larabr 2024-10-18 17:50:52 +02:00
parent 05fbc63732
commit 88f20974dd
4 changed files with 279 additions and 290 deletions

18
package-lock.json generated
View File

@ -24,6 +24,7 @@
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-wasm": "^6.2.2",
"@types/chai": "^4.3.19",
"@types/sinon": "^17.0.3",
"@typescript-eslint/parser": "^7.18.0",
"argon2id": "^1.0.1",
"benchmark": "^2.1.4",
@ -1531,6 +1532,23 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"node_modules/@types/sinon": {
"version": "17.0.3",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz",
"integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/sinonjs__fake-timers": "*"
}
},
"node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz",
"integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",

View File

@ -77,6 +77,7 @@
"@rollup/plugin-typescript": "^11.1.6",
"@rollup/plugin-wasm": "^6.2.2",
"@types/chai": "^4.3.19",
"@types/sinon": "^17.0.3",
"@typescript-eslint/parser": "^7.18.0",
"argon2id": "^1.0.1",
"benchmark": "^2.1.4",

View File

@ -12,6 +12,7 @@ import * as packet from '../../src/packet';
import * as random from '../../src/crypto/random';
import * as input from './testInputs.js';
import { mockCryptoRandomGenerator, restoreCryptoRandomGenerator } from '../mockRandom.ts';
function stringify(array) {
if (stream.isStream(array)) {
@ -287,9 +288,6 @@ export default () => describe('Packet', function() {
it('AEAD Encrypted Data packet test vector (AEADP)', async function() {
// From https://gitlab.com/openpgp-wg/rfc4880bis/commit/00b20923e6233fb6ff1666ecd5acfefceb32907d
const nodeCrypto = util.getNodeCrypto();
if (!nodeCrypto) return;
const packetBytes = util.hexToUint8Array(`
d4 4a 01 07 01 0e b7 32 37 9f 73 c4 92 8d e2 5f
ac fe 65 17 ec 10 5d c1 1a 81 dc 0c b8 a2 f6 f3
@ -311,12 +309,16 @@ export default () => describe('Packet', function() {
const msg = new openpgp.PacketList();
msg.push(enc);
const msg2 = new openpgp.PacketList();
const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes');
randomBytesStub.returns(iv);
try {
const msg2 = new openpgp.PacketList();
const randomBytesStub = sinon.stub();
randomBytesStub.onCall(0).returns(iv);
// no more random calls expected, so we throw as a reminder to update behavior if needed in the future
randomBytesStub.throws('random mock behavior not defined');
mockCryptoRandomGenerator(randomBytesStub);
await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 14 });
const data = msg.write();
expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes);
@ -324,92 +326,92 @@ export default () => describe('Packet', function() {
await msg2[0].decrypt(algo, key);
expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data);
} finally {
randomBytesStub.restore();
restoreCryptoRandomGenerator();
}
});
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, ''))
// 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, ''))
}];
testVectors.forEach(testVector => it(testVector.algoLabel, async function () {
const nodeCrypto = util.getNodeCrypto();
if (!nodeCrypto) return;
const symmetricAlgo = openpgp.enums.symmetric.aes128;
const { aeadAlgo: expectedAEADAlgo, padding, salt, sessionKey, encryptedPacketBytes: expectedEncryptedPacketBytes } = testVector;
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 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 {
const randomBytesStub = sinon.stub();
randomBytesStub.onCall(0).returns(padding);
randomBytesStub.onCall(1).returns(salt);
// no more random calls expected, so we throw as a reminder to update behavior if needed in the future
randomBytesStub.onCall(2).throws('random mock behavior not defined');
mockCryptoRandomGenerator(randomBytesStub);
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);
await seipdv2.encrypt(symmetricAlgo, sessionKey, { ...openpgp.config, aeadChunkSizeByte: 6 });
const data = msg.write();
expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(expectedEncryptedPacketBytes);
@ -420,7 +422,7 @@ export default () => describe('Packet', function() {
await msg2[0].decrypt(symmetricAlgo, sessionKey);
expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data);
} finally {
randomBytesStub.restore();
restoreCryptoRandomGenerator();
}
}));
});
@ -735,256 +737,195 @@ export default () => describe('Packet', function() {
}
});
it('Sym. encrypted session key reading/writing test vector (AEAD, EAX)', async function() {
// From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-eax-encryption-and-decryption
describe('Sym. encrypted session key reading/writing test vector (SKESK with AEADP)', () => {
const testVectors = [{
// From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-eax-encryption-and-decryption
algoLabel: 'EAX',
aeadAlgo: openpgp.enums.aead.eax,
salt: util.hexToUint8Array('cd5a9f70fbe0bc65'),
sessionKey: util.hexToUint8Array('86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d'.replace(/\s+/g, '')),
sessionIV: util.hexToUint8Array('bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35'.replace(/\s+/g, '')),
dataIV: util.hexToUint8Array('b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10'.replace(/\s+/g, '')),
packetBytes: util.hexToUint8Array(`
c3 3e 05 07 01 03 08 cd 5a 9f 70 fb e0 bc 65 90
bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35
9d ee 19 d0 7c 34 46 c4 31 2a 34 ae 19 67 a2 fb
7e 92 8e a5 b4 fa 80 12 bd 45 6d 17 38 c6 3c 36
const nodeCrypto = util.getNodeCrypto();
if (!nodeCrypto) return;
d4 4a 01 07 01 0e b7 32 37 9f 73 c4 92 8d e2 5f
ac fe 65 17 ec 10 5d c1 1a 81 dc 0c b8 a2 f6 f3
d9 00 16 38 4a 56 fc 82 1a e1 1a e8 db cb 49 86
26 55 de a8 8d 06 a8 14 86 80 1b 0f f3 87 bd 2e
ab 01 3d e1 25 95 86 90 6e ab 24 76
`.replace(/\s+/g, ''))
},
{
algoLabel: 'OCB',
aeadAlgo: openpgp.enums.aead.ocb,
salt: util.hexToUint8Array('9f0b7da3e5ea6477'),
sessionKey: util.hexToUint8Array('d1 f0 1b a3 0e 13 0a a7 d2 58 2c 16 e0 50 ae 44'.replace(/\s+/g, '')),
sessionIV: util.hexToUint8Array('99 e3 26 e5 40 0a 90 93 6c ef b4 e8 eb a0 8c'.replace(/\s+/g, '')),
dataIV: util.hexToUint8Array('5e d2 bc 1e 47 0a be 8f 1d 64 4c 7a 6c 8a 56'.replace(/\s+/g, '')),
packetBytes: util.hexToUint8Array(`
c3 3d 05 07 02 03 08 9f 0b 7d a3 e5 ea 64 77 90
99 e3 26 e5 40 0a 90 93 6c ef b4 e8 eb a0 8c 67
73 71 6d 1f 27 14 54 0a 38 fc ac 52 99 49 da c5
29 d3 de 31 e1 5b 4a eb 72 9e 33 00 33 db ed
d4 49 01 07 02 0e 5e d2 bc 1e 47 0a be 8f 1d 64
4c 7a 6c 8a 56 7b 0f 77 01 19 66 11 a1 54 ba 9c
25 74 cd 05 62 84 a8 ef 68 03 5c 62 3d 93 cc 70
8a 43 21 1b b6 ea f2 b2 7f 7c 18 d5 71 bc d8 3b
20 ad d3 a0 8b 73 af 15 b9 a0 98
`.replace(/\s+/g, ''))
}];
const aeadProtectVal = openpgp.config.aeadProtect;
const aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte;
const s2kIterationCountByteVal = openpgp.config.s2kIterationCountByte;
openpgp.config.aeadProtect = true;
openpgp.config.aeadChunkSizeByte = 14;
openpgp.config.s2kIterationCountByte = 0x90;
testVectors.forEach(testVector => it(testVector.algoLabel, async function () {
const { aeadAlgo, salt, sessionKey, sessionIV, dataIV, packetBytes: expectedEncryptedPacketBytes } = testVector;
const salt = util.hexToUint8Array('cd5a9f70fbe0bc65');
const sessionKey = util.hexToUint8Array('86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d'.replace(/\s+/g, ''));
const sessionIV = util.hexToUint8Array('bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35'.replace(/\s+/g, ''));
const dataIV = util.hexToUint8Array('b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10'.replace(/\s+/g, ''));
try {
const passphrase = 'password';
const algo = openpgp.enums.symmetric.aes128;
const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes');
randomBytesStub.onCall(0).returns(salt);
randomBytesStub.onCall(1).returns(sessionKey);
randomBytesStub.onCall(2).returns(sessionIV);
randomBytesStub.onCall(3).returns(dataIV);
const randomBytesStub = sinon.stub();
randomBytesStub.onCall(0).returns(salt);
randomBytesStub.onCall(1).returns(sessionKey);
randomBytesStub.onCall(2).returns(sessionIV);
randomBytesStub.onCall(3).returns(dataIV);
// no more random calls expected, so we throw as a reminder to update behavior if needed in the future
randomBytesStub.onCall(4).throws('random mock behavior not defined');
mockCryptoRandomGenerator(randomBytesStub);
const packetBytes = util.hexToUint8Array(`
c3 3e 05 07 01 03 08 cd 5a 9f 70 fb e0 bc 65 90
bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35
9d ee 19 d0 7c 34 46 c4 31 2a 34 ae 19 67 a2 fb
7e 92 8e a5 b4 fa 80 12 bd 45 6d 17 38 c6 3c 36
const literal = new openpgp.LiteralDataPacket(0);
literal.setBytes(util.stringToUint8Array('Hello, world!\n'), openpgp.enums.literal.binary);
literal.filename = '';
const skesk = new openpgp.SymEncryptedSessionKeyPacket();
skesk.version = 5;
skesk.sessionKeyAlgorithm = algo;
const encData = new openpgp.AEADEncryptedDataPacket();
encData.packets = new openpgp.PacketList();
encData.packets.push(literal);
encData.aeadAlgorithm = skesk.aeadAlgorithm = aeadAlgo;
const msg = new openpgp.PacketList();
msg.push(skesk);
msg.push(encData);
d4 4a 01 07 01 0e b7 32 37 9f 73 c4 92 8d e2 5f
ac fe 65 17 ec 10 5d c1 1a 81 dc 0c b8 a2 f6 f3
d9 00 16 38 4a 56 fc 82 1a e1 1a e8 db cb 49 86
26 55 de a8 8d 06 a8 14 86 80 1b 0f f3 87 bd 2e
ab 01 3d e1 25 95 86 90 6e ab 24 76
`.replace(/\s+/g, ''));
await skesk.encrypt(passphrase, { ...openpgp.config, s2kIterationCountByte: 0x90 });
try {
const passphrase = 'password';
const algo = openpgp.enums.symmetric.aes128;
const key = skesk.sessionKey;
await encData.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 14 });
const literal = new openpgp.LiteralDataPacket(0);
literal.setBytes(util.stringToUint8Array('Hello, world!\n'), openpgp.enums.literal.binary);
literal.filename = '';
const skesk = new openpgp.SymEncryptedSessionKeyPacket();
skesk.version = 5;
skesk.sessionKeyAlgorithm = algo;
const encData = new openpgp.AEADEncryptedDataPacket();
encData.packets = new openpgp.PacketList();
encData.packets.push(literal);
encData.aeadAlgorithm = skesk.aeadAlgorithm = openpgp.enums.aead.eax;
const msg = new openpgp.PacketList();
msg.push(skesk);
msg.push(encData);
const data = msg.write();
expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(expectedEncryptedPacketBytes);
await skesk.encrypt(passphrase, openpgp.config);
const msg2 = new openpgp.PacketList();
await msg2.read(data, allAllowedPackets);
const key = skesk.sessionKey;
await encData.encrypt(algo, key, undefined, openpgp.config);
await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey;
await msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2);
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 (AEAD, OCB)', async function() {
// From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-ocb-encryption-and-decryption
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 = 14;
openpgp.config.s2kIterationCountByte = 0x90;
const salt = util.hexToUint8Array('9f0b7da3e5ea6477');
const sessionKey = util.hexToUint8Array('d1 f0 1b a3 0e 13 0a a7 d2 58 2c 16 e0 50 ae 44'.replace(/\s+/g, ''));
const sessionIV = util.hexToUint8Array('99 e3 26 e5 40 0a 90 93 6c ef b4 e8 eb a0 8c'.replace(/\s+/g, ''));
const dataIV = util.hexToUint8Array('5e d2 bc 1e 47 0a be 8f 1d 64 4c 7a 6c 8a 56'.replace(/\s+/g, ''));
const randomBytesStub = sinon.stub(nodeCrypto, 'randomBytes');
randomBytesStub.onCall(0).returns(salt);
randomBytesStub.onCall(1).returns(sessionKey);
randomBytesStub.onCall(2).returns(sessionIV);
randomBytesStub.onCall(3).returns(dataIV);
const packetBytes = util.hexToUint8Array(`
c3 3d 05 07 02 03 08 9f 0b 7d a3 e5 ea 64 77 90
99 e3 26 e5 40 0a 90 93 6c ef b4 e8 eb a0 8c 67
73 71 6d 1f 27 14 54 0a 38 fc ac 52 99 49 da c5
29 d3 de 31 e1 5b 4a eb 72 9e 33 00 33 db ed
d4 49 01 07 02 0e 5e d2 bc 1e 47 0a be 8f 1d 64
4c 7a 6c 8a 56 7b 0f 77 01 19 66 11 a1 54 ba 9c
25 74 cd 05 62 84 a8 ef 68 03 5c 62 3d 93 cc 70
8a 43 21 1b b6 ea f2 b2 7f 7c 18 d5 71 bc d8 3b
20 ad d3 a0 8b 73 af 15 b9 a0 98
`.replace(/\s+/g, ''));
try {
const passphrase = 'password';
const algo = openpgp.enums.symmetric.aes128;
const literal = new openpgp.LiteralDataPacket(0);
literal.setBytes(util.stringToUint8Array('Hello, world!\n'), openpgp.enums.literal.binary);
literal.filename = '';
const skesk = new openpgp.SymEncryptedSessionKeyPacket();
skesk.version = 5;
skesk.sessionKeyAlgorithm = algo;
const enc = new openpgp.AEADEncryptedDataPacket();
enc.packets = new openpgp.PacketList();
enc.packets.push(literal);
enc.aeadAlgorithm = skesk.aeadAlgorithm = openpgp.enums.aead.ocb;
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();
}
expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data));
} finally {
restoreCryptoRandomGenerator();
}
}));
});
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, ''))
// 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, ''))
}];
testVectors.forEach(testVector => it(testVector.algoLabel, async function () {
const nodeCrypto = util.getNodeCrypto();
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 passphrase = 'password';
const symmetricAlgo = openpgp.enums.symmetric.aes128;
try {
const passphrase = 'password';
const symmetricAlgo = openpgp.enums.symmetric.aes128;
const randomBytesStub = sinon.stub();
randomBytesStub.onCall(0).returns(s2kSalt);
randomBytesStub.onCall(1).returns(sessionKey);
randomBytesStub.onCall(2).returns(sessionIV);
// no more random calls expected, so we throw as a reminder to update behavior if needed in the future
randomBytesStub.onCall(3).throws('random mock behavior not defined');
mockCryptoRandomGenerator(randomBytesStub);
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();
restoreCryptoRandomGenerator();
}
}));
})
});
it('Secret key encryption/decryption test', async function() {
const armored_msg =

29
test/mockRandom.ts Normal file
View File

@ -0,0 +1,29 @@
import type { SinonStub } from 'sinon';
import util from '../src/util';
const webcrypto = typeof crypto !== 'undefined' ? crypto : util.nodeRequire('crypto')?.webcrypto;
type GetRandomValuesFn = typeof crypto.getRandomValues;
let original: GetRandomValuesFn | null = null;
/**
* Mock `crypto.getRandomValues` using the mocked implementation
*/
export const mockCryptoRandomGenerator = (
mockedImplementation: GetRandomValuesFn | SinonStub) => {
if (original !== null) {
throw new Error('random mock already initialized');
}
original = webcrypto.getRandomValues;
webcrypto.getRandomValues = mockedImplementation;
};
export const restoreCryptoRandomGenerator = () => {
if (!original) {
throw new Error('random mock was not initialized');
}
webcrypto.getRandomValues = original;
original = null;
};