From f9a3e54364437d15c77c2fcca1c2255ad3a2add3 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:16:40 +0100 Subject: [PATCH] `openpgp.sign`: add `recipientKeys` option to get the signing prefs from If given, the signature will be generated using the preferred hash algo from the recipient keys. Otherwise, the signing key preferences are used (this was also the existing behavior). Note: when signing through `openpgp.encrypt`, the `encryptionKeys` are automatically used as recipient keys. --- src/openpgp.js | 10 ++++++---- test/general/openpgp.js | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/openpgp.js b/src/openpgp.js index 5237a8f3..22ac9077 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -392,21 +392,23 @@ export async function decrypt({ message, decryptionKeys, passwords, sessionKeys, * @param {Object} options * @param {CleartextMessage|Message} options.message - (cleartext) message to be signed * @param {PrivateKey|PrivateKey[]} options.signingKeys - Array of keys or single key with decrypted secret key data to sign cleartext + * @param {Key|Key[]} options.recipientKeys - Array of keys or single to get the signing preferences from * @param {'armored'|'binary'|'object'} [options.format='armored'] - Format of the returned message * @param {Boolean} [options.detached=false] - If the return value should contain a detached signature * @param {KeyID|KeyID[]} [options.signingKeyIDs=latest-created valid signing (sub)keys] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i] * @param {Date} [options.date=current date] - Override the creation date of the signature * @param {Object|Object[]} [options.signingUserIDs=primary user IDs] - Array of user IDs to sign with, one per key in `signingKeys`, e.g. `[{ name: 'Steve Sender', email: 'steve@openpgp.org' }]` + * @param {Object|Object[]} [options.recipientUserIDs=primary user IDs] - Array of user IDs to get the signing preferences from, one per key in `recipientKeys` * @param {Object|Object[]} [options.signatureNotations=[]] - Array of notations to add to the signatures, e.g. `[{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true, critical: false }]` * @param {Object} [options.config] - Custom configuration settings to overwrite those in [config]{@link module:config} * @returns {Promise>} Signed message (string if `armor` was true, the default; Uint8Array if `armor` was false). * @async * @static */ -export async function sign({ message, signingKeys, format = 'armored', detached = false, signingKeyIDs = [], date = new Date(), signingUserIDs = [], signatureNotations = [], config, ...rest }) { +export async function sign({ message, signingKeys, recipientKeys = [], format = 'armored', detached = false, signingKeyIDs = [], date = new Date(), signingUserIDs = [], recipientUserIDs = [], signatureNotations = [], config, ...rest }) { config = { ...defaultConfig, ...config }; checkConfig(config); checkCleartextOrMessage(message); checkOutputMessageFormat(format); - signingKeys = toArray(signingKeys); signingKeyIDs = toArray(signingKeyIDs); signingUserIDs = toArray(signingUserIDs); signatureNotations = toArray(signatureNotations); + signingKeys = toArray(signingKeys); signingKeyIDs = toArray(signingKeyIDs); signingUserIDs = toArray(signingUserIDs); recipientKeys = toArray(recipientKeys); recipientUserIDs = toArray(recipientUserIDs); signatureNotations = toArray(signatureNotations); if (rest.privateKeys) throw new Error('The `privateKeys` option has been removed from openpgp.sign, pass `signingKeys` instead'); if (rest.armor !== undefined) throw new Error('The `armor` option has been removed from openpgp.sign, pass `format` instead.'); @@ -422,9 +424,9 @@ export async function sign({ message, signingKeys, format = 'armored', detached try { let signature; if (detached) { - signature = await message.signDetached(signingKeys, [], undefined, signingKeyIDs, date, signingUserIDs, undefined, signatureNotations, config); + signature = await message.signDetached(signingKeys, recipientKeys, undefined, signingKeyIDs, date, signingUserIDs, recipientUserIDs, signatureNotations, config); } else { - signature = await message.sign(signingKeys, [], undefined, signingKeyIDs, date, signingUserIDs, undefined, signatureNotations, config); + signature = await message.sign(signingKeys, recipientKeys, undefined, signingKeyIDs, date, signingUserIDs, recipientUserIDs, signatureNotations, config); } if (format === 'object') return signature; diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 1f2d6438..3d380d92 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -2162,6 +2162,47 @@ aOU= }); expect(await stream.readToEnd(streamedData)).to.equal(text); }); + + it('should sign using hash algorithm preferred by `recipientKeys` if given', async function() { + const signingKeyWithoutSHA3Pref = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEZyID/RYJKwYBBAHaRw8BAQdAcdaUl/UXEQaT6rKNSEPmyKypikz9rIsf +BlFAQYjtsF8AAQDiW9ls2uBBRa3vA1Odl0NNNguRBolWhR9XGpdXnVBF3w5E +zQ48dGVzdEB0ZXN0Lml0PsLAEQQTFgoAgwWCZyID/QMLCQcJkJuH6wXn78D5 +RRQAAAAAABwAIHNhbHRAbm90YXRpb25zLm9wZW5wZ3Bqcy5vcmcNolfauRaj +NnItFJ0TOsiyZZhd6bMWVR4032v64tYRywMVCAoEFgACAQIZAQKbAwIeARYh +BGsOUiBRfu57iwuxh5uH6wXn78D5AACEQQEAz4YXoEKgOElvxRrIrkglUlpb +ilLZVU6mXqLxRSEtZi0BAK5xooNiLYbjF42eJuCDWUWriXufI9acT/vnruFr +p34Px10EZyID/RIKKwYBBAGXVQEFAQEHQOC8KcmOQ9+qEgoWBzc8xNgPUvoe +IVNw+mHbljD9eFBfAwEIBwAA/3iHMqnBfuM/c9tOIWKI4advW92aMYnjexrU +HdzPS2IoEU3CvgQYFgoAcAWCZyID/QmQm4frBefvwPlFFAAAAAAAHAAgc2Fs +dEBub3RhdGlvbnMub3BlbnBncGpzLm9yZ5M4VuJhTqDkHF/14D0i/wL8GTtM +fm9AIukMoYWXjGSGApsMFiEEaw5SIFF+7nuLC7GHm4frBefvwPkAAL0QAP9Z +oR7Vxyfuje3vAyEbef1gyfMN/RkIVbMKSiwy3A2W9AEA6QcBF5zUvwmHPpA4 ++SkLLMuq/yUGT6WhAq6kASQ8vgM= +=lluz +-----END PGP PRIVATE KEY BLOCK-----` }); + const recipientKeyWithSHA3Pref = await openpgp.readKey({ armoredKey: priv_key_sha3_512 }); + + const text = 'Hello, world.'; + const message = await openpgp.createCleartextMessage({ text }); + + // SHA3-512 is first preference of recipient key, and should be picked, + // even if not declared in the signing key prefs + const cleartextMessage = await openpgp.sign({ + message, + signingKeys: signingKeyWithoutSHA3Pref, + recipientKeys: recipientKeyWithSHA3Pref, + format: 'armored', + // the preferred hash algo is expected to picked when supported by the recipient keys + config: { preferredHashAlgorithm: openpgp.enums.hash.sha3_512 } + }); + const parsedArmored = await openpgp.readCleartextMessage({ cleartextMessage }); + expect(parsedArmored.signature.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1); + expect( + parsedArmored.signature.packets.filterByTag(openpgp.enums.packet.signature)[0].hashAlgorithm + ).to.equal(openpgp.enums.hash.sha3_512); + }); }); describe('encrypt - unit tests', function() {