Add fromUserId / toUserId parameters to openpgp.encrypt and sign
To select the user whose algorithm preferences, expiration time etc to use.
This commit is contained in:
parent
6c2fec3450
commit
fe3c1b4f31
|
@ -70,11 +70,12 @@ CleartextMessage.prototype.getSigningKeyIds = function() {
|
||||||
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
|
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
|
||||||
* @param {Signature} signature (optional) any existing detached signature
|
* @param {Signature} signature (optional) any existing detached signature
|
||||||
* @param {Date} date (optional) The creation time of the signature that should be created
|
* @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' }
|
||||||
* @returns {Promise<module:cleartext.CleartextMessage>} new cleartext message with signed content
|
* @returns {Promise<module:cleartext.CleartextMessage>} new cleartext message with signed content
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
CleartextMessage.prototype.sign = async function(privateKeys, signature=null, date=new Date()) {
|
CleartextMessage.prototype.sign = async function(privateKeys, signature=null, date=new Date(), userId={}) {
|
||||||
return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date));
|
return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date, userId));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,14 +83,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 {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
|
||||||
* @param {Signature} signature (optional) any existing detached signature
|
* @param {Signature} signature (optional) any existing detached signature
|
||||||
* @param {Date} date (optional) The creation time of the signature that should be created
|
* @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' }
|
||||||
* @returns {Promise<module:signature.Signature>} new detached signature of message content
|
* @returns {Promise<module:signature.Signature>} new detached signature of message content
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
CleartextMessage.prototype.signDetached = async function(privateKeys, signature=null, date=new Date()) {
|
CleartextMessage.prototype.signDetached = async function(privateKeys, signature=null, date=new Date(), userId={}) {
|
||||||
const literalDataPacket = new packet.Literal();
|
const literalDataPacket = new packet.Literal();
|
||||||
literalDataPacket.setText(this.text);
|
literalDataPacket.setText(this.text);
|
||||||
|
|
||||||
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date));
|
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userId));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
51
src/key.js
51
src/key.js
|
@ -279,14 +279,15 @@ function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) {
|
||||||
* Returns first key packet or key packet by given keyId that is available for signing and verification
|
* Returns first key packet or key packet by given keyId that is available for signing and verification
|
||||||
* @param {module:type/keyid} keyId, optional
|
* @param {module:type/keyid} keyId, optional
|
||||||
* @param {Date} date use the given date for verification instead of the current time
|
* @param {Date} date use the given date for verification instead of the current time
|
||||||
|
* @param {Object} userId, optional user ID
|
||||||
* @returns {Promise<module:packet.SecretSubkey|
|
* @returns {Promise<module:packet.SecretSubkey|
|
||||||
* module:packet.SecretKey|null>} key packet or null if no signing key has been found
|
* module:packet.SecretKey|null>} key packet or null if no signing key has been found
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date()) {
|
Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date(), userId={}) {
|
||||||
const primaryKey = this.primaryKey;
|
const primaryKey = this.primaryKey;
|
||||||
if (await this.verifyPrimaryKey(date) === enums.keyStatus.valid) {
|
if (await this.verifyPrimaryKey(date, userId) === enums.keyStatus.valid) {
|
||||||
const primaryUser = await this.getPrimaryUser(date);
|
const primaryUser = await this.getPrimaryUser(date, userId);
|
||||||
if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) &&
|
if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) &&
|
||||||
isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification, date)) {
|
isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification, date)) {
|
||||||
return primaryKey;
|
return primaryKey;
|
||||||
|
@ -322,15 +323,16 @@ function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) {
|
||||||
* Returns first key packet or key packet by given keyId that is available for encryption or decryption
|
* Returns first key packet or key packet by given keyId that is available for encryption or decryption
|
||||||
* @param {module:type/keyid} keyId, optional
|
* @param {module:type/keyid} keyId, optional
|
||||||
* @param {Date} date, optional
|
* @param {Date} date, optional
|
||||||
|
* @param {String} userId, optional
|
||||||
* @returns {Promise<module:packet.PublicSubkey|
|
* @returns {Promise<module:packet.PublicSubkey|
|
||||||
* module:packet.SecretSubkey|
|
* module:packet.SecretSubkey|
|
||||||
* module:packet.SecretKey|
|
* module:packet.SecretKey|
|
||||||
* module:packet.PublicKey|null>} key packet or null if no encryption key has been found
|
* module:packet.PublicKey|null>} key packet or null if no encryption key has been found
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date()) {
|
Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date(), userId={}) {
|
||||||
const primaryKey = this.primaryKey;
|
const primaryKey = this.primaryKey;
|
||||||
if (await this.verifyPrimaryKey(date) === enums.keyStatus.valid) {
|
if (await this.verifyPrimaryKey(date, userId) === enums.keyStatus.valid) {
|
||||||
// V4: by convention subkeys are preferred for encryption service
|
// V4: by convention subkeys are preferred for encryption service
|
||||||
// V3: keys MUST NOT have subkeys
|
// V3: keys MUST NOT have subkeys
|
||||||
for (let i = 0; i < this.subKeys.length; i++) {
|
for (let i = 0; i < this.subKeys.length; i++) {
|
||||||
|
@ -345,7 +347,7 @@ Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if no valid subkey for encryption, evaluate primary key
|
// if no valid subkey for encryption, evaluate primary key
|
||||||
const primaryUser = await this.getPrimaryUser(date);
|
const primaryUser = await this.getPrimaryUser(date, userId);
|
||||||
if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) &&
|
if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) &&
|
||||||
isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification, date)) {
|
isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification, date)) {
|
||||||
return primaryKey;
|
return primaryKey;
|
||||||
|
@ -433,10 +435,11 @@ Key.prototype.isRevoked = async function(signature, key, date=new Date()) {
|
||||||
* Verify primary key. Checks for revocation signatures, expiration time
|
* Verify primary key. Checks for revocation signatures, expiration time
|
||||||
* and valid self signature
|
* and valid self signature
|
||||||
* @param {Date} date (optional) use the given date for verification instead of the current time
|
* @param {Date} date (optional) use the given date for verification instead of the current time
|
||||||
|
* @param {Object} userId (optional) user ID
|
||||||
* @returns {Promise<module:enums.keyStatus>} The status of the primary key
|
* @returns {Promise<module:enums.keyStatus>} The status of the primary key
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Key.prototype.verifyPrimaryKey = async function(date=new Date()) {
|
Key.prototype.verifyPrimaryKey = async function(date=new Date(), userId={}) {
|
||||||
const primaryKey = this.primaryKey;
|
const primaryKey = this.primaryKey;
|
||||||
// check for key revocation signatures
|
// check for key revocation signatures
|
||||||
if (await this.isRevoked(null, null, date)) {
|
if (await this.isRevoked(null, null, date)) {
|
||||||
|
@ -448,7 +451,7 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) {
|
||||||
return enums.keyStatus.no_self_cert;
|
return enums.keyStatus.no_self_cert;
|
||||||
}
|
}
|
||||||
// check for valid, unrevoked, unexpired self signature
|
// check for valid, unrevoked, unexpired self signature
|
||||||
const { user, selfCertification } = await this.getPrimaryUser(date) || {};
|
const { user, selfCertification } = await this.getPrimaryUser(date, userId) || {};
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return enums.keyStatus.invalid;
|
return enums.keyStatus.invalid;
|
||||||
}
|
}
|
||||||
|
@ -482,19 +485,29 @@ Key.prototype.getExpirationTime = async function() {
|
||||||
* - if multiple primary users exist, returns the one with the latest self signature
|
* - if multiple primary users exist, returns the one with the latest self signature
|
||||||
* - otherwise, returns the user with the latest self signature
|
* - otherwise, returns the user with the latest self signature
|
||||||
* @param {Date} date use the given date for verification instead of the current time
|
* @param {Date} date use the given date for verification instead of the current time
|
||||||
|
* @param {Object} userId (optional) user ID to get instead of the primary user, if it exists
|
||||||
* @returns {Promise<{user: module:key.User,
|
* @returns {Promise<{user: module:key.User,
|
||||||
* selfCertification: module:packet.Signature}>} The primary user and the self signature
|
* selfCertification: module:packet.Signature}>} The primary user and the self signature
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Key.prototype.getPrimaryUser = async function(date=new Date()) {
|
Key.prototype.getPrimaryUser = async function(date=new Date(), userId={}) {
|
||||||
// sort by primary user flag and signature creation time
|
if (!this.users.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// sort by userId, primary user flag and signature creation time
|
||||||
const primaryUser = this.users.map(function(user, index) {
|
const primaryUser = this.users.map(function(user, index) {
|
||||||
const selfCertification = getLatestSignature(user.selfCertifications, date);
|
const selfCertification = getLatestSignature(user.selfCertifications, date);
|
||||||
return { index, user, selfCertification };
|
return { index, user, selfCertification };
|
||||||
}).sort(function(a, b) {
|
}).sort(function(a, b) {
|
||||||
const A = a.selfCertification;
|
const A = a.selfCertification;
|
||||||
const B = b.selfCertification;
|
const B = b.selfCertification;
|
||||||
return (A.isPrimaryUserID - B.isPrimaryUserID) || (A.created - B.created);
|
return (
|
||||||
|
(a.user.userId.email === userId.email) - (b.user.userId.email === userId.email) ||
|
||||||
|
(a.user.userId.name === userId.name) - (b.user.userId.name === userId.name) ||
|
||||||
|
(a.user.userId.comment === userId.comment) - (b.user.userId.comment === userId.comment) ||
|
||||||
|
A.isPrimaryUserID - B.isPrimaryUserID ||
|
||||||
|
A.created - B.created
|
||||||
|
);
|
||||||
}).pop();
|
}).pop();
|
||||||
const { user, selfCertification: cert } = primaryUser;
|
const { user, selfCertification: cert } = primaryUser;
|
||||||
if (!user.userId) {
|
if (!user.userId) {
|
||||||
|
@ -1403,21 +1416,22 @@ function getExpirationTime(keyPacket, signature) {
|
||||||
* Returns the preferred signature hash algorithm of a key
|
* Returns the preferred signature hash algorithm of a key
|
||||||
* @param {object} key
|
* @param {object} key
|
||||||
* @param {Date} date (optional) use the given date for verification instead of the current time
|
* @param {Date} date (optional) use the given date for verification instead of the current time
|
||||||
|
* @param {Object} userId (optional) user ID
|
||||||
* @returns {Promise<String>}
|
* @returns {Promise<String>}
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
export async function getPreferredHashAlgo(key, date) {
|
export async function getPreferredHashAlgo(key, date=new Date(), userId={}) {
|
||||||
let hash_algo = config.prefer_hash_algorithm;
|
let hash_algo = config.prefer_hash_algorithm;
|
||||||
let pref_algo = hash_algo;
|
let pref_algo = hash_algo;
|
||||||
if (key instanceof Key) {
|
if (key instanceof Key) {
|
||||||
const primaryUser = await key.getPrimaryUser(date);
|
const primaryUser = await key.getPrimaryUser(date, userId);
|
||||||
if (primaryUser && primaryUser.selfCertification.preferredHashAlgorithms) {
|
if (primaryUser && primaryUser.selfCertification.preferredHashAlgorithms) {
|
||||||
[pref_algo] = primaryUser.selfCertification.preferredHashAlgorithms;
|
[pref_algo] = primaryUser.selfCertification.preferredHashAlgorithms;
|
||||||
hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ?
|
hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ?
|
||||||
pref_algo : hash_algo;
|
pref_algo : hash_algo;
|
||||||
}
|
}
|
||||||
// disable expiration checks
|
// disable expiration checks
|
||||||
key = key.getSigningKeyPacket(undefined, null);
|
key = key.getSigningKeyPacket(undefined, null, userId);
|
||||||
}
|
}
|
||||||
switch (Object.getPrototypeOf(key)) {
|
switch (Object.getPrototypeOf(key)) {
|
||||||
case packet.SecretKey.prototype:
|
case packet.SecretKey.prototype:
|
||||||
|
@ -1440,15 +1454,16 @@ export async function getPreferredHashAlgo(key, date) {
|
||||||
* @param {symmetric|aead} type Type of preference to return
|
* @param {symmetric|aead} type Type of preference to return
|
||||||
* @param {Array<module:key.Key>} keys Set of keys
|
* @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 {Date} date (optional) use the given date for verification instead of the current time
|
||||||
|
* @param {Object} userId (optional) user ID
|
||||||
* @returns {Promise<module:enums.symmetric>} Preferred symmetric algorithm
|
* @returns {Promise<module:enums.symmetric>} Preferred symmetric algorithm
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
export async function getPreferredAlgo(type, keys, date) {
|
export async function getPreferredAlgo(type, keys, date=new Date(), userId={}) {
|
||||||
const prefProperty = type === 'symmetric' ? 'preferredSymmetricAlgorithms' : 'preferredAeadAlgorithms';
|
const prefProperty = type === 'symmetric' ? 'preferredSymmetricAlgorithms' : 'preferredAeadAlgorithms';
|
||||||
const defaultAlgo = type === 'symmetric' ? config.encryption_cipher : config.aead_mode;
|
const defaultAlgo = type === 'symmetric' ? config.encryption_cipher : config.aead_mode;
|
||||||
const prioMap = {};
|
const prioMap = {};
|
||||||
await Promise.all(keys.map(async function(key) {
|
await Promise.all(keys.map(async function(key) {
|
||||||
const primaryUser = await key.getPrimaryUser(date);
|
const primaryUser = await key.getPrimaryUser(date, userId);
|
||||||
if (!primaryUser || !primaryUser.selfCertification[prefProperty]) {
|
if (!primaryUser || !primaryUser.selfCertification[prefProperty]) {
|
||||||
return defaultAlgo;
|
return defaultAlgo;
|
||||||
}
|
}
|
||||||
|
@ -1480,11 +1495,11 @@ export async function getPreferredAlgo(type, keys, date) {
|
||||||
* @returns {Promise<Boolean>}
|
* @returns {Promise<Boolean>}
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
export async function isAeadSupported(keys, date) {
|
export async function isAeadSupported(keys, date=new Date(), userId={}) {
|
||||||
let supported = true;
|
let supported = true;
|
||||||
// TODO replace when Promise.some or Promise.any are implemented
|
// TODO replace when Promise.some or Promise.any are implemented
|
||||||
await Promise.all(keys.map(async function(key) {
|
await Promise.all(keys.map(async function(key) {
|
||||||
const primaryUser = await key.getPrimaryUser(date);
|
const primaryUser = await key.getPrimaryUser(date, userId);
|
||||||
if (!primaryUser || !primaryUser.selfCertification.features ||
|
if (!primaryUser || !primaryUser.selfCertification.features ||
|
||||||
!(primaryUser.selfCertification.features[0] & enums.features.aead)) {
|
!(primaryUser.selfCertification.features[0] & enums.features.aead)) {
|
||||||
supported = false;
|
supported = false;
|
||||||
|
|
|
@ -247,10 +247,11 @@ Message.prototype.getText = function() {
|
||||||
* @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] }
|
* @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 {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 {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' }
|
||||||
* @returns {Promise<Message>} new message with encrypted content
|
* @returns {Promise<Message>} new message with encrypted content
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date()) {
|
Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date(), userId={}) {
|
||||||
let symAlgo;
|
let symAlgo;
|
||||||
let aeadAlgo;
|
let aeadAlgo;
|
||||||
let symEncryptedPacket;
|
let symEncryptedPacket;
|
||||||
|
@ -263,9 +264,9 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
|
||||||
aeadAlgo = sessionKey.aeadAlgorithm;
|
aeadAlgo = sessionKey.aeadAlgorithm;
|
||||||
sessionKey = sessionKey.data;
|
sessionKey = sessionKey.data;
|
||||||
} else if (keys && keys.length) {
|
} else if (keys && keys.length) {
|
||||||
symAlgo = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date));
|
symAlgo = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userId));
|
||||||
if (config.aead_protect && config.aead_protect_version === 4 && await isAeadSupported(keys, date)) {
|
if (config.aead_protect && config.aead_protect_version === 4 && await isAeadSupported(keys, date, userId)) {
|
||||||
aeadAlgo = enums.read(enums.aead, await getPreferredAlgo('aead', keys, date));
|
aeadAlgo = enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userId));
|
||||||
}
|
}
|
||||||
} else if (passwords && passwords.length) {
|
} else if (passwords && passwords.length) {
|
||||||
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
|
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
|
||||||
|
@ -278,7 +279,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
|
||||||
sessionKey = await crypto.generateSessionKey(symAlgo);
|
sessionKey = await crypto.generateSessionKey(symAlgo);
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date);
|
const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date, userId);
|
||||||
|
|
||||||
if (config.aead_protect && (config.aead_protect_version !== 4 || aeadAlgo)) {
|
if (config.aead_protect && (config.aead_protect_version !== 4 || aeadAlgo)) {
|
||||||
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
|
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
|
||||||
|
@ -312,16 +313,17 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard
|
||||||
* @param {Array<Key>} publicKeys (optional) public key(s) for message encryption
|
* @param {Array<Key>} publicKeys (optional) public key(s) for message encryption
|
||||||
* @param {Array<String>} passwords (optional) for message encryption
|
* @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 {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
|
||||||
* @param {Date} date (optional) override the creation date signature
|
* @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' }
|
||||||
* @returns {Promise<Message>} new message with encrypted content
|
* @returns {Promise<Message>} new message with encrypted content
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKeys, passwords, wildcard=false, date=new Date()) {
|
export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKeys, passwords, wildcard=false, date=new Date(), userId={}) {
|
||||||
const packetlist = new packet.List();
|
const packetlist = new packet.List();
|
||||||
|
|
||||||
if (publicKeys) {
|
if (publicKeys) {
|
||||||
const results = await Promise.all(publicKeys.map(async function(publicKey) {
|
const results = await Promise.all(publicKeys.map(async function(publicKey) {
|
||||||
const encryptionKeyPacket = await publicKey.getEncryptionKeyPacket(undefined, date);
|
const encryptionKeyPacket = await publicKey.getEncryptionKeyPacket(undefined, date, userId);
|
||||||
if (!encryptionKeyPacket) {
|
if (!encryptionKeyPacket) {
|
||||||
throw new Error('Could not find valid key packet for encryption in key ' +
|
throw new Error('Could not find valid key packet for encryption in key ' +
|
||||||
publicKey.primaryKey.getKeyId().toHex());
|
publicKey.primaryKey.getKeyId().toHex());
|
||||||
|
@ -380,11 +382,12 @@ export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKey
|
||||||
* Sign the message (the literal data packet of the message)
|
* Sign the message (the literal data packet of the message)
|
||||||
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
|
* @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 {Signature} signature (optional) any existing detached signature to add to the message
|
||||||
* @param {Date} date} (optional) override the creation time of the 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' }
|
||||||
* @returns {Promise<Message>} new message with signed content
|
* @returns {Promise<Message>} new message with signed content
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date()) {
|
Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date(), userId={}) {
|
||||||
const packetlist = new packet.List();
|
const packetlist = new packet.List();
|
||||||
|
|
||||||
const literalDataPacket = this.packets.findPacket(enums.packet.literal);
|
const literalDataPacket = this.packets.findPacket(enums.packet.literal);
|
||||||
|
@ -418,14 +421,14 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new
|
||||||
if (privateKey.isPublic()) {
|
if (privateKey.isPublic()) {
|
||||||
throw new Error('Need private key for signing');
|
throw new Error('Need private key for signing');
|
||||||
}
|
}
|
||||||
const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date);
|
const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId);
|
||||||
if (!signingKeyPacket) {
|
if (!signingKeyPacket) {
|
||||||
throw new Error('Could not find valid key packet for signing in key ' +
|
throw new Error('Could not find valid key packet for signing in key ' +
|
||||||
privateKey.primaryKey.getKeyId().toHex());
|
privateKey.primaryKey.getKeyId().toHex());
|
||||||
}
|
}
|
||||||
const onePassSig = new packet.OnePassSignature();
|
const onePassSig = new packet.OnePassSignature();
|
||||||
onePassSig.type = signatureType;
|
onePassSig.type = signatureType;
|
||||||
onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, date);
|
onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId);
|
||||||
onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
|
onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
|
||||||
onePassSig.signingKeyId = signingKeyPacket.getKeyId();
|
onePassSig.signingKeyId = signingKeyPacket.getKeyId();
|
||||||
if (i === privateKeys.length - 1) {
|
if (i === privateKeys.length - 1) {
|
||||||
|
@ -467,15 +470,16 @@ Message.prototype.compress = function(compression) {
|
||||||
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
|
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
|
||||||
* @param {Signature} signature (optional) any existing detached signature
|
* @param {Signature} signature (optional) any existing detached signature
|
||||||
* @param {Date} date (optional) override the creation time of the 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' }
|
||||||
* @returns {Promise<module:signature.Signature>} new detached signature of message content
|
* @returns {Promise<module:signature.Signature>} new detached signature of message content
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date()) {
|
Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date(), userId={}) {
|
||||||
const literalDataPacket = this.packets.findPacket(enums.packet.literal);
|
const literalDataPacket = this.packets.findPacket(enums.packet.literal);
|
||||||
if (!literalDataPacket) {
|
if (!literalDataPacket) {
|
||||||
throw new Error('No literal data packet to sign.');
|
throw new Error('No literal data packet to sign.');
|
||||||
}
|
}
|
||||||
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date));
|
return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userId));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -484,10 +488,11 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null,
|
||||||
* @param {Array<module:key.Key>} privateKeys private keys with decrypted secret key data for signing
|
* @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 {Signature} signature (optional) any existing detached signature to append
|
||||||
* @param {Date} date (optional) override the creationtime of the signature
|
* @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' }
|
||||||
* @returns {Promise<module:packet.List>} list of signature packets
|
* @returns {Promise<module:packet.List>} list of signature packets
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date()) {
|
export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date(), userId={}) {
|
||||||
const packetlist = new packet.List();
|
const packetlist = new packet.List();
|
||||||
|
|
||||||
// If data packet was created from Uint8Array, use binary, otherwise use text
|
// If data packet was created from Uint8Array, use binary, otherwise use text
|
||||||
|
@ -498,7 +503,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
|
||||||
if (privateKey.isPublic()) {
|
if (privateKey.isPublic()) {
|
||||||
throw new Error('Need private key for signing');
|
throw new Error('Need private key for signing');
|
||||||
}
|
}
|
||||||
const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date);
|
const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId);
|
||||||
if (!signingKeyPacket) {
|
if (!signingKeyPacket) {
|
||||||
throw new Error('Could not find valid key packet for signing in key ' +
|
throw new Error('Could not find valid key packet for signing in key ' +
|
||||||
privateKey.primaryKey.getKeyId().toHex());
|
privateKey.primaryKey.getKeyId().toHex());
|
||||||
|
@ -509,7 +514,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
|
||||||
const signaturePacket = new packet.Signature(date);
|
const signaturePacket = new packet.Signature(date);
|
||||||
signaturePacket.signatureType = signatureType;
|
signaturePacket.signatureType = signatureType;
|
||||||
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
|
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
|
||||||
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date);
|
signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId);
|
||||||
await signaturePacket.sign(signingKeyPacket, literalDataPacket);
|
await signaturePacket.sign(signingKeyPacket, literalDataPacket);
|
||||||
return signaturePacket;
|
return signaturePacket;
|
||||||
})).then(signatureList => {
|
})).then(signatureList => {
|
||||||
|
|
|
@ -228,17 +228,19 @@ export function encryptKey({ privateKey, passphrase }) {
|
||||||
* @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object
|
* @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 {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 and the message signature
|
* @param {Date} date (optional) override the creation date of the message and 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' }
|
||||||
* @returns {Promise<Object>} encrypted (and optionally signed message) in the form:
|
* @returns {Promise<Object>} encrypted (and optionally signed message) in the form:
|
||||||
* {data: ASCII armored message if 'armor' is true,
|
* {data: ASCII armored message if 'armor' is true,
|
||||||
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
||||||
* @async
|
* @async
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
export function encrypt({ data, dataType, publicKeys, privateKeys, passwords, sessionKey, filename, compression=config.compression, armor=true, detached=false, signature=null, returnSessionKey=false, wildcard=false, date=new Date()}) {
|
export function encrypt({ data, dataType, publicKeys, privateKeys, passwords, sessionKey, filename, compression=config.compression, armor=true, detached=false, signature=null, returnSessionKey=false, wildcard=false, date=new Date(), fromUserId={}, toUserId={} }) {
|
||||||
checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
|
checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
|
||||||
|
|
||||||
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
|
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||||
return asyncProxy.delegate('encrypt', { data, dataType, publicKeys, privateKeys, passwords, sessionKey, filename, compression, armor, detached, signature, returnSessionKey, wildcard, date });
|
return asyncProxy.delegate('encrypt', { data, dataType, publicKeys, privateKeys, passwords, sessionKey, filename, compression, armor, detached, signature, returnSessionKey, wildcard, date, fromUserId, toUserId });
|
||||||
}
|
}
|
||||||
const result = {};
|
const result = {};
|
||||||
return Promise.resolve().then(async function() {
|
return Promise.resolve().then(async function() {
|
||||||
|
@ -248,14 +250,14 @@ export function encrypt({ data, dataType, publicKeys, privateKeys, passwords, se
|
||||||
}
|
}
|
||||||
if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified
|
if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified
|
||||||
if (detached) {
|
if (detached) {
|
||||||
const detachedSignature = await message.signDetached(privateKeys, signature, date);
|
const detachedSignature = await message.signDetached(privateKeys, signature, date, fromUserId);
|
||||||
result.signature = armor ? detachedSignature.armor() : detachedSignature;
|
result.signature = armor ? detachedSignature.armor() : detachedSignature;
|
||||||
} else {
|
} else {
|
||||||
message = await message.sign(privateKeys, signature, date);
|
message = await message.sign(privateKeys, signature, date, fromUserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message = message.compress(compression);
|
message = message.compress(compression);
|
||||||
return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date);
|
return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserId);
|
||||||
|
|
||||||
}).then(encrypted => {
|
}).then(encrypted => {
|
||||||
if (armor) {
|
if (armor) {
|
||||||
|
@ -322,19 +324,20 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
|
||||||
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
|
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
|
||||||
* @param {Boolean} detached (optional) if the return value should contain a detached signature
|
* @param {Boolean} detached (optional) if the return value should contain a detached signature
|
||||||
* @param {Date} date (optional) override the creation date signature
|
* @param {Date} date (optional) override the creation date signature
|
||||||
|
* @param {Object} fromUserId (optional) user ID to sign with, e.g. { name:'Steve Sender', email:'steve@openpgp.org' }
|
||||||
* @returns {Promise<Object>} signed cleartext in the form:
|
* @returns {Promise<Object>} signed cleartext in the form:
|
||||||
* {data: ASCII armored message if 'armor' is true,
|
* {data: ASCII armored message if 'armor' is true,
|
||||||
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
||||||
* @async
|
* @async
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
export function sign({ data, dataType, privateKeys, armor=true, detached=false, date=new Date() }) {
|
export function sign({ data, dataType, privateKeys, armor=true, detached=false, date=new Date(), fromUserId={} }) {
|
||||||
checkData(data);
|
checkData(data);
|
||||||
privateKeys = toArray(privateKeys);
|
privateKeys = toArray(privateKeys);
|
||||||
|
|
||||||
if (asyncProxy) { // use web worker if available
|
if (asyncProxy) { // use web worker if available
|
||||||
return asyncProxy.delegate('sign', {
|
return asyncProxy.delegate('sign', {
|
||||||
data, dataType, privateKeys, armor, detached, date
|
data, dataType, privateKeys, armor, detached, date, fromUserId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,10 +346,10 @@ export function sign({ data, dataType, privateKeys, armor=true, detached=false,
|
||||||
let message = util.isString(data) ? new CleartextMessage(data) : messageLib.fromBinary(data, dataType);
|
let message = util.isString(data) ? new CleartextMessage(data) : messageLib.fromBinary(data, dataType);
|
||||||
|
|
||||||
if (detached) {
|
if (detached) {
|
||||||
const signature = await message.signDetached(privateKeys, undefined, date);
|
const signature = await message.signDetached(privateKeys, undefined, date, fromUserId);
|
||||||
result.signature = armor ? signature.armor() : signature;
|
result.signature = armor ? signature.armor() : signature;
|
||||||
} else {
|
} else {
|
||||||
message = await message.sign(privateKeys, undefined, date);
|
message = await message.sign(privateKeys, undefined, date, fromUserId);
|
||||||
if (armor) {
|
if (armor) {
|
||||||
result.data = message.armor();
|
result.data = message.armor();
|
||||||
} else {
|
} else {
|
||||||
|
@ -403,20 +406,22 @@ export function verify({ message, publicKeys, signature=null, date=new Date() })
|
||||||
* @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, used to encrypt the key
|
* @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, used to encrypt the key
|
||||||
* @param {String|Array<String>} passwords (optional) passwords for the message
|
* @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 {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' }
|
||||||
* @returns {Promise<Message>} the encrypted session key packets contained in a message object
|
* @returns {Promise<Message>} the encrypted session key packets contained in a message object
|
||||||
* @async
|
* @async
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard=false }) {
|
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);
|
checkBinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords);
|
||||||
|
|
||||||
if (asyncProxy) { // use web worker if available
|
if (asyncProxy) { // use web worker if available
|
||||||
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard });
|
return asyncProxy.delegate('encryptSessionKey', { data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserId });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve().then(async function() {
|
return Promise.resolve().then(async function() {
|
||||||
|
|
||||||
return { message: await messageLib.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard) };
|
return { message: await messageLib.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserId) };
|
||||||
|
|
||||||
}).catch(onError.bind(null, 'Error encrypting session key'));
|
}).catch(onError.bind(null, 'Error encrypting session key'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1523,6 +1523,59 @@ p92yZgB3r2+f6/GIe2+7
|
||||||
expect(signatures[3].valid).to.be.null;
|
expect(signatures[3].valid).to.be.null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Encrypt - latest created user', async function() {
|
||||||
|
let publicKey = openpgp.key.readArmored(multi_uid_key).keys[0];
|
||||||
|
const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||||
|
await privateKey.decrypt('hello world');
|
||||||
|
// Set second user to prefer aes128. We should select this user by default, since it was created later.
|
||||||
|
publicKey.users[1].selfCertifications[0].preferredSymmetricAlgorithms = [openpgp.enums.symmetric.aes128];
|
||||||
|
const encrypted = await openpgp.encrypt({data: 'hello', publicKeys: publicKey, privateKeys: privateKey, armor: false});
|
||||||
|
expect(encrypted.message.packets[0].sessionKeyAlgorithm).to.equal('aes128');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Encrypt - primary user', async function() {
|
||||||
|
let publicKey = openpgp.key.readArmored(multi_uid_key).keys[0];
|
||||||
|
const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||||
|
await privateKey.decrypt('hello world');
|
||||||
|
// Set first user to primary. We should select this user by default.
|
||||||
|
publicKey.users[0].selfCertifications[0].isPrimaryUserID = true;
|
||||||
|
// Set first user to prefer aes128.
|
||||||
|
publicKey.users[0].selfCertifications[0].preferredSymmetricAlgorithms = [openpgp.enums.symmetric.aes128];
|
||||||
|
const encrypted = await openpgp.encrypt({data: 'hello', publicKeys: publicKey, privateKeys: privateKey, armor: false});
|
||||||
|
expect(encrypted.message.packets[0].sessionKeyAlgorithm).to.equal('aes128');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Encrypt - specific user', async function() {
|
||||||
|
let publicKey = openpgp.key.readArmored(multi_uid_key).keys[0];
|
||||||
|
const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||||
|
await privateKey.decrypt('hello world');
|
||||||
|
// Set first user to primary. We won't select this user, this is to test that.
|
||||||
|
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({data: 'hello', publicKeys: publicKey, privateKeys: privateKey, toUserId: {name: 'Test User', email: 'b@c.com'}, armor: false});
|
||||||
|
expect(encrypted.message.packets[0].sessionKeyAlgorithm).to.equal('aes128');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Sign - specific user', async function() {
|
||||||
|
let publicKey = openpgp.key.readArmored(multi_uid_key).keys[0];
|
||||||
|
const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||||
|
await privateKey.decrypt('hello world');
|
||||||
|
const privateKeyClone = openpgp.key.readArmored(priv_key_rsa).keys[0];
|
||||||
|
// Duplicate user
|
||||||
|
privateKey.users.push(privateKeyClone.users[0]);
|
||||||
|
// Set first user to primary. We won't select this user, this is to test that.
|
||||||
|
privateKey.users[0].selfCertifications[0].isPrimaryUserID = true;
|
||||||
|
// Change userid of the first user so that we don't select it. This also makes this user invalid.
|
||||||
|
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({data: 'hello', privateKeys: privateKey, fromUserId: {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({data: 'hello', publicKeys: publicKey, privateKeys: privateKey, fromUserId: {name: 'Test McTestington', email: 'test@example.com'}, detached: true, armor: false});
|
||||||
|
expect(encrypted.signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512);
|
||||||
|
});
|
||||||
|
|
||||||
it('Reformat key without passphrase', function() {
|
it('Reformat key without passphrase', function() {
|
||||||
const userId1 = 'test1 <a@b.com>';
|
const userId1 = 'test1 <a@b.com>';
|
||||||
const userId2 = 'test2 <b@a.com>';
|
const userId2 = 'test2 <b@a.com>';
|
||||||
|
|
Loading…
Reference in New Issue
Block a user