Fix openpgp.verify/decrypt with expectSigned: true and format: 'binary' (#1805)

This commit is contained in:
Daniel Huigens 2024-11-11 15:42:33 +01:00 committed by GitHub
parent 3f060660c2
commit ac1bfc0d60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 307 additions and 282 deletions

View File

@ -369,6 +369,7 @@ export async function decrypt({ message, decryptionKeys, passwords, sessionKeys,
result.data, result.data,
stream.fromAsync(async () => { stream.fromAsync(async () => {
await util.anyPromise(result.signatures.map(sig => sig.verified)); await util.anyPromise(result.signatures.map(sig => sig.verified));
return format === 'binary' ? new Uint8Array() : '';
}) })
]); ]);
} }
@ -500,6 +501,7 @@ export async function verify({ message, verificationKeys, expectSigned = false,
result.data, result.data,
stream.fromAsync(async () => { stream.fromAsync(async () => {
await util.anyPromise(result.signatures.map(sig => sig.verified)); await util.anyPromise(result.signatures.map(sig => sig.verified));
return format === 'binary' ? new Uint8Array() : '';
}) })
]); ]);
} }

View File

@ -1480,6 +1480,12 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
await expect(openpgp.decrypt(decOpt)).to.be.rejectedWith('Error decrypting message: Decryption key is not decrypted.'); await expect(openpgp.decrypt(decOpt)).to.be.rejectedWith('Error decrypting message: Decryption key is not decrypted.');
}); });
['binary', 'text'].forEach(format => {
describe(`decrypt/verify with expectSigned=true, format=${format}`, function() {
const message =
format === 'binary' ? util.encodeUTF8(plaintext) :
plaintext;
it('decrypt/verify should succeed with valid signature (expectSigned=true)', async function () { it('decrypt/verify should succeed with valid signature (expectSigned=true)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key }); const publicKey = await openpgp.readKey({ armoredKey: pub_key });
const privateKey = await openpgp.decryptKey({ const privateKey = await openpgp.decryptKey({
@ -1488,7 +1494,7 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
signingKeys: privateKey, signingKeys: privateKey,
encryptionKeys: publicKey encryptionKeys: publicKey
}); });
@ -1496,9 +1502,10 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
message: await openpgp.readMessage({ armoredMessage: encrypted }), message: await openpgp.readMessage({ armoredMessage: encrypted }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
verificationKeys: publicKey, verificationKeys: publicKey,
expectSigned: true expectSigned: true,
format
}); });
expect(data).to.equal(plaintext); expect(data).to.deep.equal(message);
expect(await signatures[0].verified).to.be.true; expect(await signatures[0].verified).to.be.true;
}); });
@ -1510,14 +1517,15 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
encryptionKeys: publicKey, encryptionKeys: publicKey,
signingKeys: privateKey signingKeys: privateKey
}); });
await expect(openpgp.decrypt({ await expect(openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: encrypted }), message: await openpgp.readMessage({ armoredMessage: encrypted }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
expectSigned: true expectSigned: true,
format
})).to.be.eventually.rejectedWith(/Verification keys are required/); })).to.be.eventually.rejectedWith(/Verification keys are required/);
}); });
@ -1529,14 +1537,15 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
encryptionKeys: publicKey encryptionKeys: publicKey
}); });
await expect(openpgp.decrypt({ await expect(openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: encrypted }), message: await openpgp.readMessage({ armoredMessage: encrypted }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
verificationKeys: publicKey, verificationKeys: publicKey,
expectSigned: true expectSigned: true,
format
})).to.be.eventually.rejectedWith(/Message is not signed/); })).to.be.eventually.rejectedWith(/Message is not signed/);
}); });
@ -1549,7 +1558,7 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
encryptionKeys: publicKey, encryptionKeys: publicKey,
signingKeys: privateKey signingKeys: privateKey
}); });
@ -1557,7 +1566,8 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
message: await openpgp.readMessage({ armoredMessage: encrypted }), message: await openpgp.readMessage({ armoredMessage: encrypted }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
verificationKeys: wrongPublicKey, verificationKeys: wrongPublicKey,
expectSigned: true expectSigned: true,
format
})).to.be.eventually.rejectedWith(/Could not find signing key/); })).to.be.eventually.rejectedWith(/Could not find signing key/);
}); });
@ -1569,7 +1579,7 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
signingKeys: privateKey, signingKeys: privateKey,
encryptionKeys: publicKey encryptionKeys: publicKey
}); });
@ -1577,10 +1587,11 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }), message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
verificationKeys: publicKey, verificationKeys: publicKey,
expectSigned: true expectSigned: true,
format
}); });
const data = await stream.readToEnd(streamedData); const data = await stream.readToEnd(streamedData);
expect(data).to.equal(plaintext); expect(data).to.deep.equal(message);
expect(await signatures[0].verified).to.be.true; expect(await signatures[0].verified).to.be.true;
}); });
@ -1592,14 +1603,15 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
encryptionKeys: publicKey, encryptionKeys: publicKey,
signingKeys: privateKey signingKeys: privateKey
}); });
await expect(openpgp.decrypt({ await expect(openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }), message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
expectSigned: true expectSigned: true,
format
})).to.be.eventually.rejectedWith(/Verification keys are required/); })).to.be.eventually.rejectedWith(/Verification keys are required/);
}); });
@ -1611,14 +1623,15 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
encryptionKeys: publicKey encryptionKeys: publicKey
}); });
await expect(openpgp.decrypt({ await expect(openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }), message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
verificationKeys: publicKey, verificationKeys: publicKey,
expectSigned: true expectSigned: true,
format
})).to.be.eventually.rejectedWith(/Message is not signed/); })).to.be.eventually.rejectedWith(/Message is not signed/);
}); });
@ -1631,7 +1644,7 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
}); });
const encrypted = await openpgp.encrypt({ const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [format]: message }),
encryptionKeys: publicKey, encryptionKeys: publicKey,
signingKeys: privateKey signingKeys: privateKey
}); });
@ -1639,12 +1652,15 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }), message: await openpgp.readMessage({ armoredMessage: stream.toStream(encrypted) }),
decryptionKeys: privateKey, decryptionKeys: privateKey,
verificationKeys: wrongPublicKey, verificationKeys: wrongPublicKey,
expectSigned: true expectSigned: true,
format
}); });
await expect( await expect(
stream.readToEnd(streamedData) stream.readToEnd(streamedData)
).to.be.eventually.rejectedWith(/Could not find signing key/); ).to.be.eventually.rejectedWith(/Could not find signing key/);
}); });
});
});
it('Supports decrypting with GnuPG dummy key', async function() { it('Supports decrypting with GnuPG dummy key', async function() {
const { rejectMessageHashAlgorithms } = openpgp.config; const { rejectMessageHashAlgorithms } = openpgp.config;
@ -1792,9 +1808,73 @@ aOU=
openpgp.config.minRSABits = minRSABitsVal; openpgp.config.minRSABits = minRSABitsVal;
}); });
describe('message', function() { ['binary', 'text', 'cleartext'].forEach(format => {
verifyTests(false); describe(`verify ${format} message`, function() {
const createMessage = format === 'cleartext' ? openpgp.createCleartextMessage : openpgp.createMessage;
const readMessage = ({ armoredMessage }) => (
format === 'cleartext' ?
openpgp.readCleartextMessage({ cleartextMessage: armoredMessage }) :
openpgp.readMessage({ armoredMessage })
);
const message =
format === 'cleartext' ? util.removeTrailingSpaces(plaintext) :
format === 'binary' ? util.encodeUTF8(plaintext) :
plaintext;
const property = format === 'cleartext' ? 'text' : format;
it('verify should succeed with valid signature (expectSigned=true)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await createMessage({ [property]: message }),
signingKeys: privateKey
});
const { data, signatures } = await openpgp.verify({
message: await readMessage({ armoredMessage: signed }),
verificationKeys: publicKey,
expectSigned: true,
format
});
expect(data).to.deep.equal(message);
expect(await signatures[0].verified).to.be.true;
});
it('verify should throw on missing signature (expectSigned=true)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
await expect(openpgp.verify({
message: await createMessage({ [property]: message }),
verificationKeys: publicKey,
expectSigned: true,
format
})).to.be.eventually.rejectedWith(/Message is not signed/);
});
it('verify should throw on invalid signature (expectSigned=true)', async function () {
const wrongPublicKey = (await openpgp.readKey({ armoredKey: priv_key_2000_2008 })).toPublic();
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await createMessage({ [property]: message }),
signingKeys: privateKey
});
await expect(openpgp.verify({
message: await readMessage({ armoredMessage: signed }),
verificationKeys: wrongPublicKey,
expectSigned: true,
format
})).to.be.eventually.rejectedWith(/Could not find signing key/);
});
if (format !== 'cleartext') {
it('verify should succeed with valid signature (expectSigned=true, with streaming)', async function () { it('verify should succeed with valid signature (expectSigned=true, with streaming)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key }); const publicKey = await openpgp.readKey({ armoredKey: pub_key });
const privateKey = await openpgp.decryptKey({ const privateKey = await openpgp.decryptKey({
@ -1803,25 +1883,25 @@ aOU=
}); });
const signed = await openpgp.sign({ const signed = await openpgp.sign({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [property]: stream.toStream(message) }),
signingKeys: privateKey signingKeys: privateKey
}); });
const { data: streamedData, signatures } = await openpgp.verify({ const { data: streamedData, signatures } = await openpgp.verify({
message: await openpgp.readMessage({ armoredMessage: stream.toStream(signed) }), message: await openpgp.readMessage({ armoredMessage: stream.toStream(signed) }),
verificationKeys: publicKey, verificationKeys: publicKey,
expectSigned: true expectSigned: true,
format
}); });
const data = await stream.readToEnd(streamedData); const data = await stream.readToEnd(streamedData);
expect(data).to.equal(plaintext); expect(data).to.deep.equal(message);
expect(await signatures[0].verified).to.be.true; expect(await signatures[0].verified).to.be.true;
}); });
it('verify should throw on missing signature (expectSigned=true, with streaming)', async function () { it('verify should throw on missing signature (expectSigned=true, with streaming)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key }); const publicKey = await openpgp.readKey({ armoredKey: pub_key });
await expect(openpgp.verify({ await expect(openpgp.verify({
message: await openpgp.createMessage({ text: stream.toStream(plaintext) }), message: await openpgp.createMessage({ [property]: stream.toStream(message) }),
verificationKeys: publicKey, verificationKeys: publicKey,
expectSigned: true expectSigned: true
})).to.be.eventually.rejectedWith(/Message is not signed/); })).to.be.eventually.rejectedWith(/Message is not signed/);
@ -1835,7 +1915,7 @@ aOU=
}); });
const signed = await openpgp.sign({ const signed = await openpgp.sign({
message: await openpgp.createMessage({ text: plaintext }), message: await openpgp.createMessage({ [property]: stream.toStream(message) }),
signingKeys: privateKey signingKeys: privateKey
}); });
const { data: streamedData } = await openpgp.verify({ const { data: streamedData } = await openpgp.verify({
@ -1854,90 +1934,33 @@ aOU=
passphrase passphrase
}); });
const message = await openpgp.createMessage({ text: 'a message' }); const correctMessage = await createMessage({ [property]: message });
const armoredSignature = await openpgp.sign({ const armoredSignature = await openpgp.sign({
message, message: correctMessage,
signingKeys: privateKey, signingKeys: privateKey,
detached: true detached: true
}); });
const { signatures } = await openpgp.verify({ const { signatures } = await openpgp.verify({
message, message: correctMessage,
signature: await openpgp.readSignature({ armoredSignature }), signature: await openpgp.readSignature({ armoredSignature }),
verificationKeys: privateKey.toPublic() verificationKeys: privateKey.toPublic()
}); });
expect(await signatures[0].verified).to.be.true; expect(await signatures[0].verified).to.be.true;
// pass a different message // pass a different message
const wrongMessage =
format === 'binary' ? util.encodeUTF8('a different message') :
'a different message';
await expect(openpgp.verify({ await expect(openpgp.verify({
message: await openpgp.createMessage({ text: 'a different message' }), message: await createMessage({ [property]: wrongMessage }),
signature: await openpgp.readSignature({ armoredSignature }), signature: await openpgp.readSignature({ armoredSignature }),
verificationKeys: privateKey.toPublic(), verificationKeys: privateKey.toPublic(),
expectSigned: true expectSigned: true
})).to.be.rejectedWith(/digest did not match/); })).to.be.rejectedWith(/digest did not match/);
}); });
});
describe('cleartext message', function() {
verifyTests(true);
});
function verifyTests(useCleartext) {
const createMessage = useCleartext ? openpgp.createCleartextMessage : openpgp.createMessage;
const readMessage = ({ armoredMessage }) => (
useCleartext ?
openpgp.readCleartextMessage({ cleartextMessage: armoredMessage }) :
openpgp.readMessage({ armoredMessage })
);
const text = useCleartext ? util.removeTrailingSpaces(plaintext) : plaintext;
it('verify should succeed with valid signature (expectSigned=true)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await createMessage({ text }),
signingKeys: privateKey
});
const { data, signatures } = await openpgp.verify({
message: await readMessage({ armoredMessage: signed }),
verificationKeys: publicKey,
expectSigned: true
});
expect(data).to.equal(text);
expect(await signatures[0].verified).to.be.true;
});
it('verify should throw on missing signature (expectSigned=true)', async function () {
const publicKey = await openpgp.readKey({ armoredKey: pub_key });
await expect(openpgp.verify({
message: await createMessage({ text }),
verificationKeys: publicKey,
expectSigned: true
})).to.be.eventually.rejectedWith(/Message is not signed/);
});
it('verify should throw on invalid signature (expectSigned=true)', async function () {
const wrongPublicKey = (await openpgp.readKey({ armoredKey: priv_key_2000_2008 })).toPublic();
const privateKey = await openpgp.decryptKey({
privateKey: await openpgp.readKey({ armoredKey: priv_key }),
passphrase
});
const signed = await openpgp.sign({
message: await createMessage({ text }),
signingKeys: privateKey
});
await expect(openpgp.verify({
message: await readMessage({ armoredMessage: signed }),
verificationKeys: wrongPublicKey,
expectSigned: true
})).to.be.eventually.rejectedWith(/Could not find signing key/);
});
} }
}); });
});
});
describe('sign - unit tests', function() { describe('sign - unit tests', function() {
it('Supports signing with GnuPG dummy key', async function() { it('Supports signing with GnuPG dummy key', async function() {