Merge pull request #1802

Determine signature hash prefs based on recipient keys instead of signing key
This commit is contained in:
larabr 2024-10-31 00:16:40 +01:00 committed by GitHub
commit fb72ea449a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 323 additions and 160 deletions

View File

@ -59,20 +59,22 @@ export class CleartextMessage {
/**
* Sign the cleartext message
* @param {Array<Key>} privateKeys - private keys with decrypted secret key data for signing
* @param {Array<Key>} signingKeys - private keys with decrypted secret key data for signing
* @param {Array<Key>} recipientKeys - recipient keys to get the signing preferences from
* @param {Signature} [signature] - Any existing detached signature
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to privateKeys[i]
* @param {Date} [date] - The creation time of the signature that should be created
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array} [signingKeyIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array} [recipientUserIDs] - User IDs associated with `recipientKeys` to get the signing preferences from
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true, critical: false }]
* @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<CleartextMessage>} New cleartext message with signed content.
* @async
*/
async sign(privateKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
async sign(signingKeys, recipientKeys = [], signature = null, signingKeyIDs = [], date = new Date(), signingUserIDs = [], recipientUserIDs = [], notations = [], config = defaultConfig) {
const literalDataPacket = new LiteralDataPacket();
literalDataPacket.setText(this.text);
const newSignature = new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, signingKeyIDs, date, userIDs, notations, true, config));
const newSignature = new Signature(await createSignaturePackets(literalDataPacket, signingKeys, recipientKeys, signature, signingKeyIDs, date, signingUserIDs, recipientUserIDs, notations, true, config));
return new CleartextMessage(this.text, newSignature);
}

View File

@ -82,6 +82,9 @@ export async function generate(algo) {
*/
export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashed) {
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(getPreferredHashAlgo(algo))) {
// Enforce digest sizes:
// - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
// - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
throw new Error('Hash algorithm too weak for EdDSA.');
}
switch (algo) {
@ -129,6 +132,9 @@ export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashe
*/
export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) {
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(getPreferredHashAlgo(algo))) {
// Enforce digest sizes:
// - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
// - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
throw new Error('Hash algorithm too weak for EdDSA.');
}
switch (algo) {

View File

@ -46,7 +46,9 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed
const curve = new CurveWithOID(oid);
checkPublicPointEnconding(curve, publicKey);
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(enums.hash.sha256)) {
// Enforce digest sizes, since the constraint was already present in RFC4880bis:
// see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
// and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
throw new Error('Hash algorithm too weak for EdDSA.');
}
const { RS: signature } = await eddsaSign(enums.publicKey.ed25519, hashAlgo, message, publicKey.subarray(1), privateKey, hashed);
@ -73,6 +75,9 @@ export async function verify(oid, hashAlgo, { r, s }, m, publicKey, hashed) {
const curve = new CurveWithOID(oid);
checkPublicPointEnconding(curve, publicKey);
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(enums.hash.sha256)) {
// Enforce digest sizes, since the constraint was already present in RFC4880bis:
// see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
// and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
throw new Error('Hash algorithm too weak for EdDSA.');
}
const RS = util.concatUint8Array([r, s]);

View File

@ -246,7 +246,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
const signatureProperties = getKeySignatureProperties();
signatureProperties.signatureType = enums.signature.key;
const signaturePacket = await helper.createSignaturePacket(dataToSign, null, secretKeyPacket, signatureProperties, options.date, undefined, undefined, undefined, config);
const signaturePacket = await helper.createSignaturePacket(dataToSign, [], secretKeyPacket, signatureProperties, options.date, undefined, undefined, undefined, config);
packetlist.push(signaturePacket);
}
@ -262,7 +262,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
signatureProperties.isPrimaryUserID = true;
}
const signaturePacket = await helper.createSignaturePacket(dataToSign, null, secretKeyPacket, signatureProperties, options.date, undefined, undefined, undefined, config);
const signaturePacket = await helper.createSignaturePacket(dataToSign, [], secretKeyPacket, signatureProperties, options.date, undefined, undefined, undefined, config);
return { userIDPacket, signaturePacket };
})).then(list => {
@ -286,7 +286,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
// Add revocation signature packet for creating a revocation certificate.
// This packet should be removed before returning the key.
const dataToSign = { key: secretKeyPacket };
packetlist.push(await helper.createSignaturePacket(dataToSign, null, secretKeyPacket, {
packetlist.push(await helper.createSignaturePacket(dataToSign, [], secretKeyPacket, {
signatureType: enums.signature.keyRevocation,
reasonForRevocationFlag: enums.reasonForRevocation.noReason,
reasonForRevocationString: ''

View File

@ -90,7 +90,7 @@ export async function createBindingSignature(subkey, primaryKey, options, config
const signatureProperties = { signatureType: enums.signature.subkeyBinding };
if (options.sign) {
signatureProperties.keyFlags = [enums.keyFlags.signData];
signatureProperties.embeddedSignature = await createSignaturePacket(dataToSign, null, subkey, {
signatureProperties.embeddedSignature = await createSignaturePacket(dataToSign, [], subkey, {
signatureType: enums.signature.keyBinding
}, options.date, undefined, undefined, undefined, config);
} else {
@ -100,41 +100,95 @@ export async function createBindingSignature(subkey, primaryKey, options, config
signatureProperties.keyExpirationTime = options.keyExpirationTime;
signatureProperties.keyNeverExpires = false;
}
const subkeySignaturePacket = await createSignaturePacket(dataToSign, null, primaryKey, signatureProperties, options.date, undefined, undefined, undefined, config);
const subkeySignaturePacket = await createSignaturePacket(dataToSign, [], primaryKey, signatureProperties, options.date, undefined, undefined, undefined, config);
return subkeySignaturePacket;
}
/**
* Returns the preferred signature hash algorithm of a key
* @param {Key} [key] - The key to get preferences from
* @param {SecretKeyPacket|SecretSubkeyPacket} keyPacket - key packet used for signing
* Returns the preferred signature hash algorithm for a set of keys.
* @param {Array<Key>} [targetKeys] - The keys to get preferences from
* @param {SecretKeyPacket|SecretSubkeyPacket} signingKeyPacket - key packet used for signing
* @param {Date} [date] - Use the given date for verification instead of the current time
* @param {Object} [userID] - User ID
* @param {Object} [targetUserID] - User IDs corresponding to `targetKeys` to get preferences from
* @param {Object} config - full configuration
* @returns {Promise<enums.hash>}
* @async
*/
export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), userID = {}, config) {
let hashAlgo = config.preferredHashAlgorithm;
let prefAlgo = hashAlgo;
if (key) {
const selfCertification = await key.getPrimarySelfSignature(date, userID, config);
if (selfCertification.preferredHashAlgorithms) {
[prefAlgo] = selfCertification.preferredHashAlgorithms;
hashAlgo = crypto.hash.getHashByteLength(hashAlgo) <= crypto.hash.getHashByteLength(prefAlgo) ?
prefAlgo : hashAlgo;
export async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Date(), targetUserIDs = [], config) {
/**
* If `preferredSenderAlgo` appears in the prefs of all recipients, we pick it; otherwise, we use the
* strongest supported algo (`defaultAlgo` is always implicitly supported by all keys).
* if no keys are available, `preferredSenderAlgo` is returned.
* For ECC signing key, the curve preferred hash is taken into account as well (see logic below).
*/
const defaultAlgo = enums.hash.sha256; // MUST implement
const preferredSenderAlgo = config.preferredHashAlgorithm;
const supportedAlgosPerTarget = await Promise.all(targetKeys.map(async (key, i) => {
const selfCertification = await key.getPrimarySelfSignature(date, targetUserIDs[i], config);
const targetPrefs = selfCertification.preferredHashAlgorithms;
return targetPrefs;
}));
const supportedAlgosMap = new Map(); // use Map over object to preserve numeric keys
for (const supportedAlgos of supportedAlgosPerTarget) {
for (const hashAlgo of supportedAlgos) {
try {
// ensure that `hashAlgo` is recognized/implemented by us, otherwise e.g. `getHashByteLength` will throw later on
const supportedAlgo = enums.write(enums.hash, hashAlgo);
supportedAlgosMap.set(
supportedAlgo,
supportedAlgosMap.has(supportedAlgo) ? supportedAlgosMap.get(supportedAlgo) + 1 : 1
);
} catch {}
}
}
switch (keyPacket.algorithm) {
case enums.publicKey.ecdsa:
case enums.publicKey.eddsaLegacy:
case enums.publicKey.ed25519:
case enums.publicKey.ed448:
prefAlgo = crypto.getPreferredCurveHashAlgo(keyPacket.algorithm, keyPacket.publicParams.oid);
const isSupportedHashAlgo = hashAlgo => targetKeys.length === 0 || supportedAlgosMap.get(hashAlgo) === targetKeys.length || hashAlgo === defaultAlgo;
const getStrongestSupportedHashAlgo = () => {
if (supportedAlgosMap.size === 0) {
return defaultAlgo;
}
const sortedHashAlgos = Array.from(supportedAlgosMap.keys())
.filter(hashAlgo => isSupportedHashAlgo(hashAlgo))
.sort((algoA, algoB) => crypto.hash.getHashByteLength(algoA) - crypto.hash.getHashByteLength(algoB));
const strongestHashAlgo = sortedHashAlgos[0];
// defaultAlgo is always implicilty supported, and might be stronger than the rest
return crypto.hash.getHashByteLength(strongestHashAlgo) >= crypto.hash.getHashByteLength(defaultAlgo) ? strongestHashAlgo : defaultAlgo;
};
const eccAlgos = new Set([
enums.publicKey.ecdsa,
enums.publicKey.eddsaLegacy,
enums.publicKey.ed25519,
enums.publicKey.ed448
]);
if (eccAlgos.has(signingKeyPacket.algorithm)) {
// For ECC, the returned hash algo MUST be at least as strong as `preferredCurveHashAlgo`, see:
// - ECDSA: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.2-5
// - EdDSALegacy: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
// - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
// - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
// Hence, we return the `preferredHashAlgo` as long as it's supported and strong enough;
// Otherwise, we look at the strongest supported algo, and ultimately fallback to the curve
// preferred algo, even if not supported by all targets.
const preferredCurveAlgo = crypto.getPreferredCurveHashAlgo(signingKeyPacket.algorithm, signingKeyPacket.publicParams.oid);
const preferredSenderAlgoIsSupported = isSupportedHashAlgo(preferredSenderAlgo);
const preferredSenderAlgoStrongerThanCurveAlgo = crypto.hash.getHashByteLength(preferredSenderAlgo) >= crypto.hash.getHashByteLength(preferredCurveAlgo);
if (preferredSenderAlgoIsSupported && preferredSenderAlgoStrongerThanCurveAlgo) {
return preferredSenderAlgo;
} else {
const strongestSupportedAlgo = getStrongestSupportedHashAlgo();
return crypto.hash.getHashByteLength(strongestSupportedAlgo) >= crypto.hash.getHashByteLength(preferredCurveAlgo) ?
strongestSupportedAlgo :
preferredCurveAlgo;
}
}
return crypto.hash.getHashByteLength(hashAlgo) <= crypto.hash.getHashByteLength(prefAlgo) ?
prefAlgo : hashAlgo;
// `preferredSenderAlgo` may be weaker than the default, but we do not guard against this,
// since it was manually set by the sender.
return isSupportedHashAlgo(preferredSenderAlgo) ? preferredSenderAlgo : getStrongestSupportedHashAlgo();
}
/**
@ -205,7 +259,7 @@ export async function getPreferredCipherSuite(keys = [], date = new Date(), user
/**
* Create signature packet
* @param {Object} dataToSign - Contains packets to be signed
* @param {PrivateKey} privateKey - key to get preferences from
* @param {Array<Key>} recipientKeys - keys to get preferences from
* @param {SecretKeyPacket|
* SecretSubkeyPacket} signingKeyPacket secret key packet for signing
* @param {Object} [signatureProperties] - Properties to write on the signature packet before signing
@ -216,7 +270,7 @@ export async function getPreferredCipherSuite(keys = [], date = new Date(), user
* @param {Object} config - full configuration
* @returns {Promise<SignaturePacket>} Signature packet.
*/
export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userID, notations = [], detached = false, config) {
export async function createSignaturePacket(dataToSign, recipientKeys, signingKeyPacket, signatureProperties, date, recipientUserIDs, notations = [], detached = false, config) {
if (signingKeyPacket.isDummy()) {
throw new Error('Cannot sign with a gnu-dummy key.');
}
@ -226,7 +280,7 @@ export async function createSignaturePacket(dataToSign, privateKey, signingKeyPa
const signaturePacket = new SignaturePacket();
Object.assign(signaturePacket, signatureProperties);
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userID, config);
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(recipientKeys, signingKeyPacket, date, recipientUserIDs, config);
signaturePacket.rawNotations = [...notations];
await signaturePacket.sign(signingKeyPacket, dataToSign, date, detached, config);
return signaturePacket;

View File

@ -206,7 +206,7 @@ class PrivateKey extends PublicKey {
}
const dataToSign = { key: this.keyPacket };
const key = this.clone();
key.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, this.keyPacket, {
key.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, [], this.keyPacket, {
signatureType: enums.signature.keyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString

View File

@ -185,7 +185,7 @@ class Subkey {
) {
const dataToSign = { key: primaryKey, bind: this.keyPacket };
const subkey = new Subkey(this.keyPacket, this.mainKey);
subkey.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, primaryKey, {
subkey.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, [], primaryKey, {
signatureType: enums.signature.subkeyRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString

View File

@ -72,7 +72,7 @@ class User {
throw new Error("The user's own key can only be used for self-certifications");
}
const signingKey = await privateKey.getSigningKey(undefined, date, undefined, config);
return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, {
return createSignaturePacket(dataToSign, [privateKey], signingKey.keyPacket, {
// Most OpenPGP implementations use generic certification (0x10)
signatureType: enums.signature.certGeneric,
keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData]
@ -260,7 +260,7 @@ class User {
key: primaryKey
};
const user = new User(dataToSign.userID || dataToSign.userAttribute, this.mainKey);
user.revocationSignatures.push(await createSignaturePacket(dataToSign, null, primaryKey, {
user.revocationSignatures.push(await createSignaturePacket(dataToSign, [], primaryKey, {
signatureType: enums.signature.certRevocation,
reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
reasonForRevocationString

View File

@ -496,16 +496,18 @@ export class Message {
/**
* Sign the message (the literal data packet of the message)
* @param {Array<PrivateKey>} signingKeys - private keys with decrypted secret key data for signing
* @param {Array<Key>} recipientKeys - recipient keys to get the signing preferences from
* @param {Signature} [signature] - Any existing detached signature to add to the message
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i]
* @param {Date} [date] - Override the creation time of the signature
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array<UserID>} [signingUserIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array<UserID>} [recipientUserIDs] - User IDs associated with `recipientKeys` to get the signing preferences from
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true, critical: false }]
* @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Message>} New message with signed content.
* @async
*/
async sign(signingKeys = [], signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
async sign(signingKeys = [], recipientKeys = [], signature = null, signingKeyIDs = [], date = new Date(), signingUserIDs = [], recipientUserIDs = [], notations = [], config = defaultConfig) {
const packetlist = new PacketList();
const literalDataPacket = this.packets.findPacket(enums.packet.literalData);
@ -513,7 +515,7 @@ export class Message {
throw new Error('No literal data packet to sign.');
}
const signaturePackets = await createSignaturePackets(literalDataPacket, signingKeys, signature, signingKeyIDs, date, userIDs, notations, false, config); // this returns the existing signature packets as well
const signaturePackets = await createSignaturePackets(literalDataPacket, signingKeys, recipientKeys, signature, signingKeyIDs, date, signingUserIDs, recipientUserIDs, notations, false, config); // this returns the existing signature packets as well
const onePassSignaturePackets = signaturePackets.map(
(signaturePacket, i) => OnePassSignaturePacket.fromSignaturePacket(signaturePacket, i === 0))
.reverse(); // innermost OPS refers to the first signature packet
@ -549,21 +551,23 @@ export class Message {
/**
* Create a detached signature for the message (the literal data packet of the message)
* @param {Array<PrivateKey>} signingKeys - private keys with decrypted secret key data for signing
* @param {Array<Key>} recipientKeys - recipient keys to get the signing preferences from
* @param {Signature} [signature] - Any existing detached signature
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i]
* @param {Date} [date] - Override the creation time of the signature
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array<UserID>} [signingUserIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array<UserID>} [recipientUserIDs] - User IDs associated with `recipientKeys` to get the signing preferences from
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true, critical: false }]
* @param {Object} [config] - Full configuration, defaults to openpgp.config
* @returns {Promise<Signature>} New detached signature of message content.
* @async
*/
async signDetached(signingKeys = [], signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
async signDetached(signingKeys = [], recipientKeys = [], signature = null, signingKeyIDs = [], recipientKeyIDs = [], date = new Date(), userIDs = [], notations = [], config = defaultConfig) {
const literalDataPacket = this.packets.findPacket(enums.packet.literalData);
if (!literalDataPacket) {
throw new Error('No literal data packet to sign.');
}
return new Signature(await createSignaturePackets(literalDataPacket, signingKeys, signature, signingKeyIDs, date, userIDs, notations, true, config));
return new Signature(await createSignaturePackets(literalDataPacket, signingKeys, recipientKeys, signature, signingKeyIDs, recipientKeyIDs, date, userIDs, notations, true, config));
}
/**
@ -698,10 +702,12 @@ export class Message {
* Create signature packets for the message
* @param {LiteralDataPacket} literalDataPacket - the literal data packet to sign
* @param {Array<PrivateKey>} [signingKeys] - private keys with decrypted secret key data for signing
* @param {Array<Key>} [recipientKeys] - recipient keys to get the signing preferences from
* @param {Signature} [signature] - Any existing detached signature to append
* @param {Array<module:type/keyid~KeyID>} [signingKeyIDs] - Array of key IDs to use for signing. Each signingKeyIDs[i] corresponds to signingKeys[i]
* @param {Date} [date] - Override the creationtime of the signature
* @param {Array} [userIDs] - User IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array<UserID>} [signingUserIDs] - User IDs to sign to, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array<UserID>} [recipientUserIDs] - User IDs associated with `recipientKeys` to get the signing preferences from
* @param {Array} [notations] - Notation Data to add to the signatures, e.g. [{ name: 'test@example.org', value: new TextEncoder().encode('test'), humanReadable: true, critical: false }]
* @param {Array} [signatureSalts] - A list of signature salts matching the number of signingKeys that should be used for v6 signatures
* @param {Boolean} [detached] - Whether to create detached signature packets
@ -710,7 +716,7 @@ export class Message {
* @async
* @private
*/
export async function createSignaturePackets(literalDataPacket, signingKeys, signature = null, signingKeyIDs = [], date = new Date(), userIDs = [], notations = [], detached = false, config = defaultConfig) {
export async function createSignaturePackets(literalDataPacket, signingKeys, recipientKeys = [], signature = null, signingKeyIDs = [], date = new Date(), signingUserIDs = [], recipientUserIDs = [], notations = [], detached = false, config = defaultConfig) {
const packetlist = new PacketList();
// If data packet was created from Uint8Array, use binary, otherwise use text
@ -718,12 +724,12 @@ export async function createSignaturePackets(literalDataPacket, signingKeys, sig
enums.signature.binary : enums.signature.text;
await Promise.all(signingKeys.map(async (primaryKey, i) => {
const userID = userIDs[i];
const signingUserID = signingUserIDs[i];
if (!primaryKey.isPrivate()) {
throw new Error('Need private key for signing');
}
const signingKey = await primaryKey.getSigningKey(signingKeyIDs[i], date, userID, config);
return createSignaturePacket(literalDataPacket, primaryKey, signingKey.keyPacket, { signatureType }, date, userID, notations, detached, config);
const signingKey = await primaryKey.getSigningKey(signingKeyIDs[i], date, signingUserID, config);
return createSignaturePacket(literalDataPacket, recipientKeys.length ? recipientKeys : [primaryKey], signingKey.keyPacket, { signatureType }, date, recipientUserIDs, notations, detached, config);
})).then(signatureList => {
packetlist.push(...signatureList);
});

View File

@ -290,7 +290,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords,
try {
if (signingKeys.length || signature) { // sign the message only if signing keys or signature is specified
message = await message.sign(signingKeys, signature, signingKeyIDs, date, signingUserIDs, signatureNotations, config);
message = await message.sign(signingKeys, encryptionKeys, signature, signingKeyIDs, date, signingUserIDs, encryptionKeyIDs, signatureNotations, config);
}
message = message.compress(
await getPreferredCompressionAlgo(encryptionKeys, date, encryptionUserIDs, config),
@ -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, 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, signatureNotations, config);
signature = await message.sign(signingKeys, recipientKeys, undefined, signingKeyIDs, date, signingUserIDs, recipientUserIDs, signatureNotations, config);
}
if (format === 'object') return signature;

View File

@ -7,7 +7,7 @@ chaiUse(chaiAsPromised);
import sinon from 'sinon';
import openpgp from '../initOpenpgp.js';
import util from '../../src/util.js';
import { getPreferredCipherSuite } from '../../src/key';
import { getPreferredCipherSuite, getPreferredHashAlgo } from '../../src/key';
import KeyID from '../../src/type/keyid.js';
const priv_key_arm2 =
@ -4136,113 +4136,152 @@ CNa5yq6lyexhsn2Vs8DsX+SOSUyNJiy5FyIJ
expect(revKey.armor()).not.to.match(/Comment: This is a revocation certificate/);
});
it('getPreferredCipherSuite - one key', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes256
describe('getPreferredCipherSuite()', () => {
it('getPreferredCipherSuite - one key', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes256
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(undefined);
});
it('getPreferredCipherSuite - two keys', async function() {
const { aes128, aes192, cast5 } = openpgp.enums.symmetric;
const [key1, key2] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key2.getPrimaryUser();
primaryUser.selfCertification.preferredSymmetricAlgorithms = [6, aes192, cast5];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes192
});
expect(symmetricAlgo).to.equal(aes192);
expect(aeadAlgo).to.equal(undefined);
const { symmetricAlgo: symmetricAlgo2, aeadAlgo: aeadAlgo2 } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes256
});
expect(symmetricAlgo2).to.equal(aes128);
expect(aeadAlgo2).to.equal(undefined);
});
it('getPreferredCipherSuite - two keys - one without pref', async function() {
const [key1, key2] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key2.getPrimaryUser();
primaryUser.selfCertification.preferredSymmetricAlgorithms = null;
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2]);
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128);
expect(aeadAlgo).to.equal(undefined);
});
it('getPreferredCipherSuite with AEAD - one key - GCM', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config,
aeadProtect: true,
preferredAEADAlgorithm: openpgp.enums.aead.gcm
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(openpgp.enums.aead.gcm);
});
it('getPreferredCipherSuite with AEAD - one key - AES256-OCB', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[openpgp.enums.symmetric.aes256, openpgp.enums.aead.ocb]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config,
aeadProtect: true,
preferredAEADAlgorithm: openpgp.enums.aead.gcm
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(openpgp.enums.aead.ocb);
});
it('getPreferredCipherSuite with AEAD - one key - AES128-GCM', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[openpgp.enums.symmetric.aes128, openpgp.enums.aead.gcm]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config,
aeadProtect: true,
preferredAEADAlgorithm: openpgp.enums.aead.gcm
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128);
expect(aeadAlgo).to.equal(openpgp.enums.aead.gcm);
});
it('getPreferredCipherSuite with AEAD - two keys - one without pref', async function() {
const keys = await openpgp.readKeys({ armoredKeys: twoKeys });
const key1 = keys[0];
const key2 = keys[1];
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]];
const primaryUser2 = await key2.getPrimaryUser();
primaryUser2.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config,
aeadProtect: true
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128);
expect(aeadAlgo).to.equal(openpgp.enums.aead.ocb);
});
it('getPreferredCipherSuite with AEAD - two keys - one with no support', async function() {
const keys = await openpgp.readKeys({ armoredKeys: twoKeys });
const key1 = keys[0];
const key2 = keys[1];
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config,
aeadProtect: true
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(undefined);
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(undefined);
});
it('getPreferredCipherSuite - two keys', async function() {
const { aes128, aes192, cast5 } = openpgp.enums.symmetric;
const [key1, key2] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key2.getPrimaryUser();
primaryUser.selfCertification.preferredSymmetricAlgorithms = [6, aes192, cast5];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes192
});
expect(symmetricAlgo).to.equal(aes192);
expect(aeadAlgo).to.equal(undefined);
const { symmetricAlgo: symmetricAlgo2, aeadAlgo: aeadAlgo2 } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes256
});
expect(symmetricAlgo2).to.equal(aes128);
expect(aeadAlgo2).to.equal(undefined);
});
describe('getPreferredHashAlgo()', () => {
it('getPreferredHashAlgo - it can handle unknown hash algorithms', async function() {
// Preferred hash algo: SHA256 and unknown algo with ID '99'
const signingKeyWithUnknownAlgoPref = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
it('getPreferredCipherSuite - two keys - one without pref', async function() {
const [key1, key2] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key2.getPrimaryUser();
primaryUser.selfCertification.preferredSymmetricAlgorithms = null;
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2]);
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128);
expect(aeadAlgo).to.equal(undefined);
});
it('getPreferredCipherSuite with AEAD - one key - GCM', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config,
aeadProtect: true,
preferredAEADAlgorithm: openpgp.enums.aead.gcm
xVgEZyJrexYJKwYBBAHaRw8BAQdAJwddYhjAmI6OzqxkW9cAXVBfZdSFxsaZ
0v9YAJA50fQAAQCK5y2PWn5MEoWnMre7WDMCv3HPs92No9r7ZrmXED3ZohDT
zQ48dGVzdEB0ZXN0Lml0PsLAEQQTFgoAgwWCZyJrewMLCQcJkPu0BwaBSfbo
RRQAAAAAABwAIHNhbHRAbm90YXRpb25zLm9wZW5wZ3Bqcy5vcmdJHbHl9Kh1
AmD2A1I0IgJEsWl12eWrRzU2C5MilKZDXQMVCGMEFgACAQIZAQKbAwIeARYh
BDc1TCI+j6hTVaLvtfu0BwaBSfboAAA3cwEAwA/JVtszZ1PgowLYG2/ok+WL
+AcEbvhPBBoJV6B2gLsA/2S/WIFiNLJd9xVPCsnlsh6GSqjNjEYXZIag0u14
WoEKx10EZyJrexIKKwYBBAGXVQEFAQEHQEnAXen/dnz9PZ+oJ9BYrDV+N/6y
c5nTJbTmMj01obBBAwEIBwAA/0izDCturSN2513OhRlrHc55biP/GL2CR6LK
e3Zo4XCoEFDCvgQYFgoAcAWCZyJrewmQ+7QHBoFJ9uhFFAAAAAAAHAAgc2Fs
dEBub3RhdGlvbnMub3BlbnBncGpzLm9yZ6bxx8jT55ZC4ZuKBMyd1j0ULyQ4
PPAbypPTzwI7bN7zApsMFiEENzVMIj6PqFNVou+1+7QHBoFJ9ugAAPRMAP9k
45AQSzIKF8JmS28I8hSUDrPCjSVh1A3Aw01F6sRYLgEA1wq81Sxnmvo6ztxK
EVdFOaJsHYaJ0A23hIaCWML5nAs=
=jJaL
-----END PGP PRIVATE KEY BLOCK-----
` });
const config = {
...openpgp.config,
preferredHashAlgorithm: openpgp.enums.hash.sha512 // SHA512 is not in the key prefs
};
const hashAlgo = await getPreferredHashAlgo(
[signingKeyWithUnknownAlgoPref],
signingKeyWithUnknownAlgoPref,
undefined,
undefined,
config
);
expect(hashAlgo).to.equal(openpgp.enums.hash.sha256);
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(openpgp.enums.aead.gcm);
});
it('getPreferredCipherSuite with AEAD - one key - AES256-OCB', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[openpgp.enums.symmetric.aes256, openpgp.enums.aead.ocb]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config,
aeadProtect: true,
preferredAEADAlgorithm: openpgp.enums.aead.gcm
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(openpgp.enums.aead.ocb);
});
it('getPreferredCipherSuite with AEAD - one key - AES128-GCM', async function() {
const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys });
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[openpgp.enums.symmetric.aes128, openpgp.enums.aead.gcm]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, {
...openpgp.config,
aeadProtect: true,
preferredAEADAlgorithm: openpgp.enums.aead.gcm
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128);
expect(aeadAlgo).to.equal(openpgp.enums.aead.gcm);
});
it('getPreferredCipherSuite with AEAD - two keys - one without pref', async function() {
const keys = await openpgp.readKeys({ armoredKeys: twoKeys });
const key1 = keys[0];
const key2 = keys[1];
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]];
const primaryUser2 = await key2.getPrimaryUser();
primaryUser2.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config,
aeadProtect: true
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128);
expect(aeadAlgo).to.equal(openpgp.enums.aead.ocb);
});
it('getPreferredCipherSuite with AEAD - two keys - one with no support', async function() {
const keys = await openpgp.readKeys({ armoredKeys: twoKeys });
const key1 = keys[0];
const key2 = keys[1];
const primaryUser = await key1.getPrimaryUser();
primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag
primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]];
const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, {
...openpgp.config,
aeadProtect: true
});
expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256);
expect(aeadAlgo).to.equal(undefined);
});
it('User attribute packet read & write', async function() {
@ -4343,7 +4382,7 @@ VYGdb3eNlV8CfoEC
privateKey.users[0].userID = openpgp.UserIDPacket.fromObject({ name: 'Test User', email: 'b@c.com' });
// Set second user to prefer aes128. We will select this user.
privateKey.users[1].selfCertifications[0].preferredHashAlgorithms = [openpgp.enums.hash.sha512];
const config = { minRSABits: 1024 };
const config = { minRSABits: 1024, preferredHashAlgorithm: openpgp.enums.hash.sha512 };
const signed = await openpgp.sign({
message: await openpgp.createMessage({ text: 'hello' }), signingKeys: privateKey, signingUserIDs: { name: 'Test McTestington', email: 'test@example.com' }, format: 'binary', config
});

View File

@ -297,7 +297,7 @@ DECl1Qu4QyeXin29uEXWiekMpNlZVsEuc8icCw6ABhIZ
=/7PI
-----END PGP PRIVATE KEY BLOCK-----`;
const priv_key_sha3 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
const priv_key_sha3_512 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
xUsGZN8edBsAAAAgdUMlFMFCVKNo7sdUd6FVBos6NNjpUpSdrodk6BfPb/kA+3bu
A2+WY2LwyxlX5o07WR2VSn+wuegC3v28yO0tClHCtwYfGw4AAABIBYJk3x50BAsJ
@ -1957,8 +1957,8 @@ aOU=
})).to.be.rejectedWith(/No signing keys provided/);
});
it('Signing with key which uses sha3 should generate a valid sha3 signature', async function() {
const privKey = await openpgp.readKey({ armoredKey: priv_key_sha3 });
it('Signing with key which uses sha3 should generate a valid sha3 signature if `config.preferredHashAlgorithm` has been set accordingly', async function() {
const privKey = await openpgp.readKey({ armoredKey: priv_key_sha3_512 });
const pubKey = privKey.toPublic();
const text = 'Hello, world.';
const message = await openpgp.createCleartextMessage({ text });
@ -1968,10 +1968,18 @@ aOU=
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.config.preferredHashAlgorithm);
const cleartextMessageWithSHA3 = await openpgp.sign({ message, signingKeys: privKey, format: 'armored', config: { preferredHashAlgorithm: openpgp.enums.hash.sha3_512 } });
const parsedArmoredSHA3 = await openpgp.readCleartextMessage({ cleartextMessage: cleartextMessageWithSHA3 });
expect(parsedArmoredSHA3.signature.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1);
expect(
parsedArmoredSHA3.signature.packets.filterByTag(openpgp.enums.packet.signature)[0].hashAlgorithm
).to.equal(openpgp.enums.hash.sha3_512);
const verified = await openpgp.verify({ message: parsedArmored, verificationKeys: pubKey, expectSigned: true });
const verifiedSHA3 = await openpgp.verify({ message: parsedArmoredSHA3, verificationKeys: pubKey, expectSigned: true });
expect(verified.data).to.equal(text);
expect(verifiedSHA3.data).to.equal(text);
});
it('should output cleartext message of expected format', async function() {
@ -2154,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() {