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.
This commit is contained in:
larabr 2024-10-29 18:16:40 +01:00
parent d3e75de23d
commit f9a3e54364
2 changed files with 47 additions and 4 deletions

View File

@ -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<MaybeStream<String|Uint8Array>>} 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;

View File

@ -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() {