Make fromUserIds/toUserIds params plural, and accept arrays of User IDs

Each User ID object is used for the key object at the corresponding index
in the privateKeys/publicKeys arrays.
This commit is contained in:
Daniel Huigens 2018-12-14 16:23:17 +01:00
parent c7339f6f78
commit 65772d86b3
5 changed files with 58 additions and 56 deletions

View File

@ -68,12 +68,12 @@ CleartextMessage.prototype.getSigningKeyIds = function() {
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature
* @param {Date} date (optional) The creation time of the signature that should be created
* @param {Object} userId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
* @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @returns {Promise<module:cleartext.CleartextMessage>} new cleartext message with signed content
* @async
*/
CleartextMessage.prototype.sign = async function(privateKeys, signature=null, date=new Date(), userId={}) {
return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date, userId));
CleartextMessage.prototype.sign = async function(privateKeys, signature=null, date=new Date(), userIds=[]) {
return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date, userIds));
};
/**
@ -81,15 +81,15 @@ CleartextMessage.prototype.sign = async function(privateKeys, signature=null, da
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature
* @param {Date} date (optional) The creation time of the signature that should be created
* @param {Object} userId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
* @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @returns {Promise<module:signature.Signature>} new detached signature of message content
* @async
*/
CleartextMessage.prototype.signDetached = async function(privateKeys, signature=null, date=new Date(), userId={}) {
CleartextMessage.prototype.signDetached = async function(privateKeys, signature=null, date=new Date(), userIds=[]) {
const literalDataPacket = new packet.Literal();
literalDataPacket.setText(this.text);
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userId));
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds));
};
/**

View File

@ -1684,16 +1684,16 @@ export async function getPreferredHashAlgo(key, keyPacket, date=new Date(), user
* @param {symmetric|aead} type Type of preference to return
* @param {Array<module:key.Key>} keys Set of keys
* @param {Date} date (optional) use the given date for verification instead of the current time
* @param {Object} userId (optional) user ID
* @param {Array} userIds (optional) user IDs
* @returns {Promise<module:enums.symmetric>} Preferred symmetric algorithm
* @async
*/
export async function getPreferredAlgo(type, keys, date=new Date(), userId={}) {
export async function getPreferredAlgo(type, keys, date=new Date(), userIds=[]) {
const prefProperty = type === 'symmetric' ? 'preferredSymmetricAlgorithms' : 'preferredAeadAlgorithms';
const defaultAlgo = type === 'symmetric' ? config.encryption_cipher : config.aead_mode;
const prioMap = {};
await Promise.all(keys.map(async function(key) {
const primaryUser = await key.getPrimaryUser(date, userId);
await Promise.all(keys.map(async function(key, i) {
const primaryUser = await key.getPrimaryUser(date, userIds[i]);
if (!primaryUser || !primaryUser.selfCertification[prefProperty]) {
return defaultAlgo;
}
@ -1722,14 +1722,15 @@ export async function getPreferredAlgo(type, keys, date=new Date(), userId={}) {
* Returns whether aead is supported by all keys in the set
* @param {Array<module:key.Key>} keys Set of keys
* @param {Date} date (optional) use the given date for verification instead of the current time
* @param {Array} userIds (optional) user IDs
* @returns {Promise<Boolean>}
* @async
*/
export async function isAeadSupported(keys, date=new Date(), userId={}) {
export async function isAeadSupported(keys, date=new Date(), userIds=[]) {
let supported = true;
// TODO replace when Promise.some or Promise.any are implemented
await Promise.all(keys.map(async function(key) {
const primaryUser = await key.getPrimaryUser(date, userId);
await Promise.all(keys.map(async function(key, i) {
const primaryUser = await key.getPrimaryUser(date, userIds[i]);
if (!primaryUser || !primaryUser.selfCertification.features ||
!(primaryUser.selfCertification.features[0] & enums.features.aead)) {
supported = false;

View File

@ -262,12 +262,12 @@ Message.prototype.getText = function() {
* @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] }
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the creation date of the literal package
* @param {Object} userId (optional) user ID to encrypt for, e.g. { name:'Robert Receiver', email:'robert@openpgp.org' }
* @param {Array} userIds (optional) user IDs to encrypt for, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }]
* @param {Boolean} streaming (optional) whether to process data as a stream
* @returns {Promise<Message>} new message with encrypted content
* @async
*/
Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date(), userId={}, streaming) {
Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date(), userIds=[], streaming) {
let symAlgo;
let aeadAlgo;
let symEncryptedPacket;
@ -280,9 +280,9 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
aeadAlgo = sessionKey.aeadAlgorithm;
sessionKey = sessionKey.data;
} else if (keys && keys.length) {
symAlgo = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userId));
if (config.aead_protect && config.aead_protect_version === 4 && await isAeadSupported(keys, date, userId)) {
aeadAlgo = enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userId));
symAlgo = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userIds));
if (config.aead_protect && config.aead_protect_version === 4 && await isAeadSupported(keys, date, userIds)) {
aeadAlgo = enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userIds));
}
} else if (passwords && passwords.length) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
@ -295,7 +295,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
sessionKey = await crypto.generateSessionKey(symAlgo);
}
const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date, userId);
const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date, userIds);
if (config.aead_protect && (config.aead_protect_version !== 4 || aeadAlgo)) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
@ -330,16 +330,16 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
* @param {Array<String>} passwords (optional) for message encryption
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the date
* @param {Object} userId (optional) user ID to encrypt for, e.g. { name:'Robert Receiver', email:'robert@openpgp.org' }
* @param {Array} userIds (optional) user IDs to encrypt for, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }]
* @returns {Promise<Message>} new message with encrypted content
* @async
*/
export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKeys, passwords, wildcard=false, date=new Date(), userId={}) {
export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKeys, passwords, wildcard=false, date=new Date(), userIds=[]) {
const packetlist = new packet.List();
if (publicKeys) {
const results = await Promise.all(publicKeys.map(async function(publicKey) {
const encryptionKey = await publicKey.getEncryptionKey(undefined, date, userId);
const encryptionKey = await publicKey.getEncryptionKey(undefined, date, userIds);
if (!encryptionKey) {
throw new Error('Could not find valid key packet for encryption in key ' +
publicKey.getKeyId().toHex());
@ -399,11 +399,11 @@ export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKey
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature to add to the message
* @param {Date} date (optional) override the creation time of the signature
* @param {Object} userId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
* @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @returns {Promise<Message>} new message with signed content
* @async
*/
Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date(), userId={}) {
Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date(), userIds=[]) {
const packetlist = new packet.List();
const literalDataPacket = this.packets.findPacket(enums.packet.literal);
@ -437,14 +437,14 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new
if (privateKey.isPublic()) {
throw new Error('Need private key for signing');
}
const signingKey = await privateKey.getSigningKey(undefined, date, userId);
const signingKey = await privateKey.getSigningKey(undefined, date, userIds);
if (!signingKey) {
throw new Error('Could not find valid key packet for signing in key ' +
privateKey.getKeyId().toHex());
}
const onePassSig = new packet.OnePassSignature();
onePassSig.signatureType = signatureType;
onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKey.keyPacket, date, userId);
onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKey.keyPacket, date, userIds);
onePassSig.publicKeyAlgorithm = signingKey.keyPacket.algorithm;
onePassSig.issuerKeyId = signingKey.getKeyId();
if (i === privateKeys.length - 1) {
@ -486,16 +486,16 @@ Message.prototype.compress = function(compression) {
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature
* @param {Date} date (optional) override the creation time of the signature
* @param {Object} userId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
* @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @returns {Promise<module:signature.Signature>} new detached signature of message content
* @async
*/
Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date(), userId={}) {
Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date(), userIds=[]) {
const literalDataPacket = this.packets.findPacket(enums.packet.literal);
if (!literalDataPacket) {
throw new Error('No literal data packet to sign.');
}
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userId));
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds));
};
/**
@ -504,18 +504,19 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null,
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature to append
* @param {Date} date (optional) override the creationtime of the signature
* @param {Object} userId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
* @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @returns {Promise<module:packet.List>} list of signature packets
* @async
*/
export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date(), userId={}) {
export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date(), userIds=[]) {
const packetlist = new packet.List();
// If data packet was created from Uint8Array, use binary, otherwise use text
const signatureType = literalDataPacket.text === null ?
enums.signature.binary : enums.signature.text;
await Promise.all(privateKeys.map(async privateKey => {
await Promise.all(privateKeys.map(async (privateKey, i) => {
const userId = userIds[i];
if (privateKey.isPublic()) {
throw new Error('Need private key for signing');
}

View File

@ -288,8 +288,8 @@ export function encryptKey({ privateKey, passphrase }) {
* @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the creation date of the message signature
* @param {Object} fromUserId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
* @param {Object} toUserId (optional) user ID to encrypt for, e.g. { name:'Robert Receiver', email:'robert@openpgp.org' }
* @param {Array} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @param {Array} toUserIds (optional) array of user IDs to encrypt for, one per key in `publicKeys`, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }]
* @returns {Promise<Object>} Object containing encrypted (and optionally signed) message in the form:
*
* {
@ -302,11 +302,11 @@ export function encryptKey({ privateKey, passphrase }) {
* @async
* @static
*/
export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, compression=config.compression, armor=true, streaming=message&&message.fromStream, detached=false, signature=null, returnSessionKey=false, wildcard=false, date=new Date(), fromUserId={}, toUserId={} }) {
checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, compression=config.compression, armor=true, streaming=message&&message.fromStream, detached=false, signature=null, returnSessionKey=false, wildcard=false, date=new Date(), fromUserIds=[], toUserIds=[] }) {
checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); fromUserIds = toArray(fromUserIds); toUserIds = toArray(toUserIds);
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
return asyncProxy.delegate('encrypt', { message, publicKeys, privateKeys, passwords, sessionKey, compression, armor, streaming, detached, signature, returnSessionKey, wildcard, date, fromUserId, toUserId });
return asyncProxy.delegate('encrypt', { message, publicKeys, privateKeys, passwords, sessionKey, compression, armor, streaming, detached, signature, returnSessionKey, wildcard, date, fromUserIds, toUserIds });
}
const result = {};
return Promise.resolve().then(async function() {
@ -315,14 +315,14 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe
}
if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified
if (detached) {
const detachedSignature = await message.signDetached(privateKeys, signature, date, fromUserId);
const detachedSignature = await message.signDetached(privateKeys, signature, date, fromUserIds);
result.signature = armor ? detachedSignature.armor() : detachedSignature;
} else {
message = await message.sign(privateKeys, signature, date, fromUserId);
message = await message.sign(privateKeys, signature, date, fromUserIds);
}
}
message = message.compress(compression);
return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserId, streaming);
return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserIds, streaming);
}).then(async encrypted => {
if (armor) {
@ -405,7 +405,7 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
* @param {'web'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any.
* @param {Boolean} detached (optional) if the return value should contain a detached signature
* @param {Date} date (optional) override the creation date of the signature
* @param {Object} fromUserId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
* @param {Array} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }]
* @returns {Promise<Object>} Object containing signed message in the form:
*
* {
@ -422,23 +422,23 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
* @async
* @static
*/
export function sign({ message, privateKeys, armor=true, streaming=message&&message.fromStream, detached=false, date=new Date(), fromUserId={} }) {
export function sign({ message, privateKeys, armor=true, streaming=message&&message.fromStream, detached=false, date=new Date(), fromUserIds=[] }) {
checkCleartextOrMessage(message);
privateKeys = toArray(privateKeys);
privateKeys = toArray(privateKeys); fromUserIds = toArray(fromUserIds);
if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('sign', {
message, privateKeys, armor, streaming, detached, date, fromUserId
message, privateKeys, armor, streaming, detached, date, fromUserIds
});
}
const result = {};
return Promise.resolve().then(async function() {
if (detached) {
const signature = await message.signDetached(privateKeys, undefined, date, fromUserId);
const signature = await message.signDetached(privateKeys, undefined, date, fromUserIds);
result.signature = armor ? signature.armor() : signature;
} else {
message = await message.sign(privateKeys, undefined, date, fromUserId);
message = await message.sign(privateKeys, undefined, date, fromUserIds);
if (armor) {
result.data = message.armor();
} else {
@ -509,21 +509,21 @@ export function verify({ message, publicKeys, streaming=message&&message.fromStr
* @param {String|Array<String>} passwords (optional) passwords for the message
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the date
* @param {Object} toUserId (optional) user ID to encrypt for, e.g. { name:'Phil Zimmermann', email:'phil@openpgp.org' }
* @param {Array} toUserIds (optional) array of user IDs to encrypt for, one per key in `publicKeys`, e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }]
* @returns {Promise<Message>} the encrypted session key packets contained in a message object
* @async
* @static
*/
export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard=false, date=new Date(), toUserId={} }) {
checkBinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords);
export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard=false, date=new Date(), toUserIds=[] }) {
checkBinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords); toUserIds = toArray(toUserIds);
if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserId });
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserIds });
}
return Promise.resolve().then(async function() {
return { message: await messageLib.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserId) };
return { message: await messageLib.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserIds) };
}).catch(onError.bind(null, 'Error encrypting session key'));
}

View File

@ -2271,9 +2271,9 @@ VYGdb3eNlV8CfoEC
publicKey.users[0].selfCertifications[0].isPrimaryUserID = true;
// Set second user to prefer aes128. We will select this user.
publicKey.users[1].selfCertifications[0].preferredSymmetricAlgorithms = [openpgp.enums.symmetric.aes128];
const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserId: {name: 'Test User', email: 'b@c.com'}, armor: false});
const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserIds: {name: 'Test User', email: 'b@c.com'}, armor: false});
expect(encrypted.message.packets[0].sessionKeyAlgorithm).to.equal('aes128');
await expect(openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserId: {name: 'Test User', email: 'c@c.com'}, armor: false})).to.be.rejectedWith('Could not find user that matches that user ID');
await expect(openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserIds: {name: 'Test User', email: 'c@c.com'}, armor: false})).to.be.rejectedWith('Could not find user that matches that user ID');
});
it('Sign - specific user', async function() {
@ -2289,11 +2289,11 @@ VYGdb3eNlV8CfoEC
privateKey.users[0].userId.parse('Test User <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 signed = await openpgp.sign({message: openpgp.cleartext.fromText('hello'), privateKeys: privateKey, fromUserId: {name: 'Test McTestington', email: 'test@example.com'}, armor: false});
const signed = await openpgp.sign({message: openpgp.cleartext.fromText('hello'), privateKeys: privateKey, fromUserIds: {name: 'Test McTestington', email: 'test@example.com'}, armor: false});
expect(signed.message.signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512);
const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, fromUserId: {name: 'Test McTestington', email: 'test@example.com'}, detached: true, armor: false});
const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, fromUserIds: {name: 'Test McTestington', email: 'test@example.com'}, detached: true, armor: false});
expect(encrypted.signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512);
await expect(openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, fromUserId: {name: 'Not Test McTestington', email: 'test@example.com'}, detached: true, armor: false})).to.be.rejectedWith('Could not find user that matches that user ID');
await expect(openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, fromUserIds: {name: 'Not Test McTestington', email: 'test@example.com'}, detached: true, armor: false})).to.be.rejectedWith('Could not find user that matches that user ID');
});
it('Find a valid subkey binding signature among many invalid ones', async function() {