From ec22dabac3f4bff038347ab542970b702845e3e0 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Sun, 4 Mar 2018 07:00:44 -0800 Subject: [PATCH 1/6] Slightly simplifies key.js; adds key.verifyKeyPackets which should be run before getEncryption/SigningKeyPacket --- src/config/config.js | 2 + src/crypto/public_key/elliptic/key.js | 7 - src/key.js | 671 +++++++++++++------------- src/message.js | 30 +- src/packet/packetlist.js | 14 + src/packet/signature.js | 1 + src/type/keyid.js | 2 +- test/general/key.js | 7 +- test/general/signature.js | 6 +- 9 files changed, 373 insertions(+), 367 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 97ede95e..4747b793 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -53,6 +53,8 @@ export default { * @property {Boolean} password_collision_check */ password_collision_check: false, + /** @property {Boolean} revocations_expire If true, expired revocation signatures are ignored */ + revocations_expire: false, /** @property {Boolean} use_native Use native Node.js crypto/zlib and WebCrypto APIs when available */ use_native: true, diff --git a/src/crypto/public_key/elliptic/key.js b/src/crypto/public_key/elliptic/key.js index afa16432..ba6c26e1 100644 --- a/src/crypto/public_key/elliptic/key.js +++ b/src/crypto/public_key/elliptic/key.js @@ -234,13 +234,6 @@ const ECDSASignature = nodeCrypto ? ); }) : undefined; -const ECParameters = nodeCrypto ? - asn1.define('ECParameters', function() { - this.choice({ - namedCurve: this.objid() - }); - }) : undefined; - const ECPrivateKey = nodeCrypto ? asn1.define('ECPrivateKey', function() { this.seq().obj( diff --git a/src/key.js b/src/key.js index 8c989b96..4b045d33 100644 --- a/src/key.js +++ b/src/key.js @@ -45,12 +45,12 @@ export function Key(packetlist) { } // same data as in packetlist but in structured form this.primaryKey = null; - this.revocationSignature = null; - this.directSignatures = null; - this.users = null; - this.subKeys = null; + this.revocationSignatures = []; + this.directSignatures = []; + this.users = []; + this.subKeys = []; this.packetlist2structure(packetlist); - if (!this.primaryKey || !this.users) { + if (!this.primaryKey || !this.users.length) { throw new Error('Invalid key: need at least key and user ID packet'); } } @@ -73,17 +73,11 @@ Key.prototype.packetlist2structure = function(packetlist) { case enums.packet.userid: case enums.packet.userAttribute: user = new User(packetlist[i]); - if (!this.users) { - this.users = []; - } this.users.push(user); break; case enums.packet.publicSubkey: case enums.packet.secretSubkey: user = null; - if (!this.subKeys) { - this.subKeys = []; - } subKey = new SubKey(packetlist[i]); this.subKeys.push(subKey); break; @@ -98,34 +92,19 @@ Key.prototype.packetlist2structure = function(packetlist) { continue; } if (packetlist[i].issuerKeyId.equals(primaryKeyId)) { - if (!user.selfCertifications) { - user.selfCertifications = []; - } user.selfCertifications.push(packetlist[i]); } else { - if (!user.otherCertifications) { - user.otherCertifications = []; - } user.otherCertifications.push(packetlist[i]); } break; case enums.signature.cert_revocation: if (user) { - if (!user.revocationCertifications) { - user.revocationCertifications = []; - } - user.revocationCertifications.push(packetlist[i]); + user.revocationSignatures.push(packetlist[i]); } else { - if (!this.directSignatures) { - this.directSignatures = []; - } this.directSignatures.push(packetlist[i]); } break; case enums.signature.key: - if (!this.directSignatures) { - this.directSignatures = []; - } this.directSignatures.push(packetlist[i]); break; case enums.signature.subkey_binding: @@ -136,14 +115,14 @@ Key.prototype.packetlist2structure = function(packetlist) { subKey.bindingSignatures.push(packetlist[i]); break; case enums.signature.key_revocation: - this.revocationSignature = packetlist[i]; + this.revocationSignatures.push(packetlist[i]); break; case enums.signature.subkey_revocation: if (!subKey) { util.print_debug('Dropping subkey revocation signature without preceding subkey packet'); continue; } - subKey.revocationSignature = packetlist[i]; + subKey.revocationSignatures.push(packetlist[i]); break; } break; @@ -158,40 +137,42 @@ Key.prototype.packetlist2structure = function(packetlist) { Key.prototype.toPacketlist = function() { const packetlist = new packet.List(); packetlist.push(this.primaryKey); - packetlist.push(this.revocationSignature); + packetlist.concat(this.revocationSignatures); packetlist.concat(this.directSignatures); - let i; - for (i = 0; i < this.users.length; i++) { - packetlist.concat(this.users[i].toPacketlist()); - } - if (this.subKeys) { - for (i = 0; i < this.subKeys.length; i++) { - packetlist.concat(this.subKeys[i].toPacketlist()); - } - } + this.users.map(user => packetlist.concat(user.toPacketlist())); + this.subKeys.map(subKey => packetlist.concat(subKey.toPacketlist())); return packetlist; }; /** - * Returns all the private and public subkey packets - * @returns {Array<(module:packet/public_subkey|module:packet/secret_subkey)>} + * Returns packetlist containing all public or private subkey packets matching keyId; + * If keyId is not present, returns all subkey packets. + * @param {type/keyid} keyId + * @returns {module:packet/packetlist} */ -Key.prototype.getSubkeyPackets = function() { - const subKeys = []; - if (this.subKeys) { - for (let i = 0; i < this.subKeys.length; i++) { - subKeys.push(this.subKeys[i].subKey); +Key.prototype.getSubkeyPackets = function(keyId=null) { + const packets = new packet.List(); + this.subKeys.forEach(subKey => { + if (!keyId || subKey.subKey.getKeyId().equals(keyId)) { + packets.push(subKey.subKey); } - } - return subKeys; + }); + return packets; }; /** - * Returns all the private and public key and subkey packets - * @returns {Array<(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key)>} + * Returns a packetlist containing all public or private key packets matching keyId. + * If keyId is not present, returns all key packets starting with the primary key. + * @param {type/keyid} keyId + * @returns {module:packet/packetlist} */ -Key.prototype.getAllKeyPackets = function() { - return [this.primaryKey].concat(this.getSubkeyPackets()); +Key.prototype.getKeyPackets = function(keyId=null) { + const packets = new packet.List(); + if (!keyId || this.primaryKey.getKeyId().equals(keyId)) { + packets.push(this.primaryKey); + } + packets.concat(this.getSubkeyPackets(keyId)); + return packets; }; /** @@ -199,32 +180,7 @@ Key.prototype.getAllKeyPackets = function() { * @returns {Array} */ Key.prototype.getKeyIds = function() { - const keyIds = []; - const keys = this.getAllKeyPackets(); - for (let i = 0; i < keys.length; i++) { - keyIds.push(keys[i].getKeyId()); - } - return keyIds; -}; - -/** - * Returns array containing first key packet for given key ID or all key packets in the case of a wildcard ID - * @param {type/keyid} keyId - * @returns {(module:packet/public_subkey|module:packet/public_key| - * module:packet/secret_subkey|module:packet/secret_key|null)} - */ -Key.prototype.getKeyPackets = function(packetKeyId) { - const keys = this.getAllKeyPackets(); - if (packetKeyId.isWildcard()) { - return keys; - } - for (let i = 0; i < keys.length; i++) { - const keyId = keys[i].getKeyId(); - if (keyId.equals(packetKeyId)) { - return [keys[i]]; - } - } - return []; + return this.getKeyPackets().map(keyPacket => keyPacket.getKeyId()); }; /** @@ -297,11 +253,25 @@ Key.prototype.armor = function() { return armor.encode(type, this.toPacketlist().write()); }; +function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) { + const normDate = util.normalizeDate(date); + return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && + keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && + keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && + (!signature.keyFlags || + (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && + signature.verified && !signature.revoked && !signature.isExpired(normDate) && + (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity))); +} + /** - * Returns first key packet or key packet by given keyId that is available for signing or signature verification + * Returns first key packet or key packet by given keyId that is available for signing and verification + * + * NOTE: call verifyKeyPackets before calling this function. * @param {module:type/keyid} keyId, optional * @param {Date} date use the given date for verification instead of the current time - * @returns {(module:packet/secret_subkey|module:packet/secret_key|null)} key packet or null if no signing key has been found + * @returns {(module:packet/secret_subkey| + module:packet/secret_key|null)} key packet or null if no signing key has been found */ Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { const primaryUser = this.getPrimaryUser(date); @@ -309,17 +279,16 @@ Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { return this.primaryKey; } - if (this.subKeys) { - for (let i = 0; i < this.subKeys.length; i++) { - if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { - for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { - if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { - return this.subKeys[i].subKey; - } + for (let i = 0; i < this.subKeys.length; i++) { + if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { + if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { + return this.subKeys[i].subKey; } } } } + // TODO throw descriptive error return null; }; @@ -332,37 +301,29 @@ function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) { (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && - (!signature.isExpired(normDate) && - (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity)))); -} - -function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) { - const normDate = util.normalizeDate(date); - return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && - keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && - keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && - (!signature.keyFlags || - (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && - (!signature.isExpired(normDate) && - (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity)))); + signature.verified && !signature.revoked && !signature.isExpired(normDate) && + (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity))); } /** * Returns first key packet or key packet by given keyId that is available for encryption or decryption + * + * NOTE: call verifyKeyPackets before calling this function. * @param {module:type/keyid} keyId, optional - * @param {Date} date optional - * @returns {(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key|null)} key packet or null if no encryption key has been found + * @param {Date} date, optional + * @returns {(module:packet/public_subkey| + * module:packet/secret_subkey| + * module:packet/secret_key| + * module:packet/public_key|null)} key packet or null if no encryption key has been found */ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { // V4: by convention subkeys are preferred for encryption service // V3: keys MUST NOT have subkeys - if (this.subKeys) { - for (let i = 0; i < this.subKeys.length; i++) { - if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { - for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { - if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { - return this.subKeys[i].subKey; - } + for (let i = 0; i < this.subKeys.length; i++) { + if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { + if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { + return this.subKeys[i].subKey; } } } @@ -373,81 +334,97 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { return this.primaryKey; } + // TODO throw descriptive error return null; }; /** - * Encrypts all secret key and subkey packets + * Encrypts all secret key and subkey packets matching keyId + * @param {module:type/keyid} keyId * @param {String} passphrase - * @returns {Promise} + * @returns {Promise>} */ -Key.prototype.encrypt = async function(passphrase) { +Key.prototype.encrypt = async function(passphrase, keyId=null) { if (!this.isPrivate()) { throw new Error("Nothing to encrypt in a public key"); } - const keys = this.getAllKeyPackets(); - await Promise.all(keys.map(async function(packet) { - await packet.encrypt(passphrase); - await packet.clearPrivateParams(); - return packet; + return Promise.all(this.getKeyPackets(keyId).map(async function(keyPacket) { + await keyPacket.encrypt(passphrase); + await keyPacket.clearPrivateParams(); + return keyPacket; })); - return true; }; /** - * Decrypts all secret key and subkey packets + * Decrypts all secret key and subkey packets matching keyId * @param {String} passphrase - * @returns {Promise} true if all key and subkey packets decrypted successfully + * @param {module:type/keyid} keyId + * @returns {Promise} true if all matching key and subkey packets decrypted successfully */ -Key.prototype.decrypt = async function(passphrase) { +Key.prototype.decrypt = async function(passphrase, keyId=null) { if (!this.isPrivate()) { throw new Error("Nothing to decrypt in a public key"); } - const keys = this.getAllKeyPackets(); - await Promise.all(keys.map(packet => packet.decrypt(passphrase))); - return true; + const results = await Promise.all(this.getKeyPackets(keyId).map(async function(keyPacket) { + return keyPacket.decrypt(passphrase); + })); + return results.every(result => result === true); }; /** - * Decrypts specific key packets by key ID - * @param {Array} keyIds - * @param {String} passphrase - * @returns {Boolean} true if all key packets decrypted successfully + * Checks if a signature on a key is revoked + * @param {module:packet/secret_key| + * @param {module:packet/signature} signature The signature to verify + * @param {module:packet/public_subkey| + * module:packet/secret_subkey| + * module:packet/public_key| + * module:packet/secret_key} key, optional The key to verify the signature + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} True if the certificate is revoked */ -Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { - if (this.isPrivate()) { - const keys = this.getAllKeyPackets(); - for (let i = 0; i < keys.length; i++) { - const keyId = keys[i].getKeyId(); - for (let j = 0; j < keyIds.length; j++) { - if (keyId.equals(keyIds[j])) { - const success = keys[i].decrypt(passphrase); - if (!success) { - return false; - } - } +Key.prototype.isRevoked = async function(signature, key, date=new Date()) { + return isDataRevoked( + this.primaryKey, { key: this.primaryKey }, this.revocationSignatures, signature, key, date + ); +}; + +/** + * Returns a packetlist containing all verified public or private key packets matching keyId. + * If keyId is not present, returns all verified key packets starting with the primary key. + * Verification is in the context of given date. + * @param {type/keyid} keyId + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} + */ +Key.prototype.verifyKeyPackets = async function(keyId=null, date=new Date()) { + const packets = new packet.List(); + const { primaryKey } = this; + if (await this.verifyPrimaryKey(date)) { + if (!keyId || primaryKey.getKeyId().equals(keyId)) { + packets.push(primaryKey); + } + } + await Promise.all(this.subKeys.map(async subKey => { + if (!keyId || subKey.subKey.getKeyId().equals(keyId)) { + if (await subKey.verify(primaryKey, date)) { + packets.push(subKey.subKey); } } - } else { - throw new Error("Nothing to decrypt in a public key"); - } - return true; + })); + return packets; }; /** * Verify primary key. Checks for revocation signatures, expiration time * and valid self signature * @param {Date} date (optional) use the given date for verification instead of the current time - * @returns {Promise{module:enums.keyStatus}} The status of the primary key + * @returns {Promise} The status of the primary key */ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { - // TODO clarify OpenPGP's behavior given an expired revocation signature - // check revocation signature - if (this.revocationSignature && !this.revocationSignature.isExpired() && - (this.revocationSignature.verified || - await this.revocationSignature.verify(this.primaryKey, { key: this.primaryKey }))) { + // check for key revocation signatures + if (await this.isRevoked(null, null, date)) { return enums.keyStatus.revoked; } const creationTime = this.primaryKey.created.getTime(); @@ -461,7 +438,7 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { } // check for at least one self signature. Self signature of user ID not mandatory // See {@link https://tools.ietf.org/html/rfc4880#section-11.1} - if (!this.users.some(user => user.userId && user.selfCertifications)) { + if (!this.users.some(user => user.userId && user.selfCertifications.length)) { return enums.keyStatus.no_self_cert; } // check for valid self signature @@ -472,7 +449,8 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { } // check V4 expiration time if (date !== null && this.primaryKey.version === 4) { - const expirationTime = primaryUser.selfCertificate.keyNeverExpires === false ? creationTime + primaryUser.selfCertificate.keyExpirationTime*1000 : Infinity; + const expirationTime = primaryUser.selfCertificate.keyNeverExpires === false ? + creationTime + primaryUser.selfCertificate.keyExpirationTime*1000 : Infinity; if (!(creationTime <= currentTime && currentTime < expirationTime)) { return enums.keyStatus.expired; } @@ -516,6 +494,8 @@ function getExpirationTime(keyPacket, selfCertificate, defaultValue=null) { * - otherwise, returns the user with the latest self signature * * NOTE: call verifyPrimaryUser before calling this function. + * This is because getPrimaryUser isn't async, so it cannot validate and instead + * relies on already validated certificates. * @param {Date} date use the given date for verification instead of the current time * @returns {{user: Array, selfCertificate: Array}|null} The primary user and the self signature */ @@ -523,7 +503,7 @@ Key.prototype.getPrimaryUser = function(date=new Date()) { let primaryUsers = []; for (let i = 0; i < this.users.length; i++) { // here we only check the primary user ID, ignoring the primary user attribute - if (!this.users[i].userId || !this.users[i].selfCertifications) { + if (!this.users[i].userId || !this.users[i].selfCertifications.length) { continue; } for (let j = 0; j < this.users[i].selfCertifications.length; j++) { @@ -548,7 +528,7 @@ Key.prototype.getPrimaryUser = function(date=new Date()) { /** * Update key with new components from specified key with same key ID: * users, subkeys, certificates are merged into the destination key, - * duplicates are ignored. + * duplicates and expired signatures are ignored. * If the specified key is a private key and the destination key is public, * the destination key is transformed to a private key. * @param {module:key~Key} key source key to merge @@ -563,24 +543,21 @@ Key.prototype.update = async function(key) { } if (this.isPublic() && key.isPrivate()) { // check for equal subkey packets - const equal = ((this.subKeys && this.subKeys.length) === (key.subKeys && key.subKeys.length)) && - (!this.subKeys || this.subKeys.every(function(destSubKey) { - return key.subKeys.some(function(srcSubKey) { - return destSubKey.subKey.getFingerprint() === srcSubKey.subKey.getFingerprint(); - }); - })); + const equal = (this.subKeys.length === key.subKeys.length) && + (this.subKeys.every(function(destSubKey) { + return key.subKeys.some(function(srcSubKey) { + return destSubKey.subKey.getFingerprint() === srcSubKey.subKey.getFingerprint(); + }); + })); if (!equal) { throw new Error('Cannot update public key with private key if subkey mismatch'); } this.primaryKey = key.primaryKey; } - // TODO clarify OpenPGP's behavior given an expired revocation signature - // revocation signature - if (!this.revocationSignature && key.revocationSignature && !key.revocationSignature.isExpired() && - (key.revocationSignature.verified || - await key.revocationSignature.verify(key.primaryKey, { key: key.primaryKey }))) { - this.revocationSignature = key.revocationSignature; - } + // revocation signatures + await mergeSignatures(key, this, 'revocationSignatures', function(srcRevSig) { + return isDataRevoked(that.primaryKey, that, [srcRevSig], null, key.primaryKey); + }); // direct signatures await mergeSignatures(key, this, 'directSignatures'); // TODO replace when Promise.some or Promise.any are implemented @@ -600,20 +577,18 @@ Key.prototype.update = async function(key) { })); // TODO replace when Promise.some or Promise.any are implemented // subkeys - if (key.subKeys) { - await Promise.all(key.subKeys.map(async function(srcSubKey) { - let found = false; - await Promise.all(that.subKeys.map(async function(dstSubKey) { - if (srcSubKey.subKey.getFingerprint() === dstSubKey.subKey.getFingerprint()) { - await dstSubKey.update(srcSubKey, that.primaryKey); - found = true; - } - })); - if (!found) { - that.subKeys.push(srcSubKey); + await Promise.all(key.subKeys.map(async function(srcSubKey) { + let found = false; + await Promise.all(that.subKeys.map(async function(dstSubKey) { + if (srcSubKey.subKey.getFingerprint() === dstSubKey.subKey.getFingerprint()) { + await dstSubKey.update(srcSubKey, that.primaryKey); + found = true; } })); - } + if (!found) { + that.subKeys.push(srcSubKey); + } + })); }; /** @@ -627,7 +602,7 @@ Key.prototype.update = async function(key) { async function mergeSignatures(source, dest, attr, checkFn) { source = source[attr]; if (source) { - if (!dest[attr]) { + if (!dest[attr].length) { dest[attr] = source; } else { await Promise.all(source.map(async function(sourceSig) { @@ -683,7 +658,8 @@ Key.prototype.signAllUsers = async function(privateKeys) { * - if no arguments are given, verifies the self certificates; * - otherwise, verifies all certificates signed with given keys. * @param {Array} keys array of keys to verify certificate signatures - * @returns {Promise>} list of signer's keyid and validity of signature + * @returns {Promise>} List of signer's keyid and validity of signature */ Key.prototype.verifyPrimaryUser = async function(keys) { const { primaryKey } = this; @@ -692,7 +668,7 @@ Key.prototype.verifyPrimaryUser = async function(keys) { let lastPrimaryUserID = null; await Promise.all(this.users.map(async function(user) { // here we verify both the primary user ID or the primary user attribute - if (!(user.userId || user.userAttribute) || !user.selfCertifications) { + if (!(user.userId || user.userAttribute) || !user.selfCertifications.length) { return; } const dataToVerify = { userid: user.userId || user.userAttribute, key: primaryKey }; @@ -733,7 +709,9 @@ Key.prototype.verifyPrimaryUser = async function(keys) { * - if no arguments are given, verifies the self certificates; * - otherwise, verifies all certificates signed with given keys. * @param {Array} keys array of keys to verify certificate signatures - * @returns {Promise>} list of userid, signer's keyid and validity of signature + * @returns {Promise>} list of userid, signer's keyid and validity of signature */ Key.prototype.verifyAllUsers = async function(keys) { const results = []; @@ -762,9 +740,9 @@ function User(userPacket) { } this.userId = userPacket.tag === enums.packet.userid ? userPacket : null; this.userAttribute = userPacket.tag === enums.packet.userAttribute ? userPacket : null; - this.selfCertifications = null; - this.otherCertifications = null; - this.revocationCertifications = null; + this.selfCertifications = []; + this.otherCertifications = []; + this.revocationSignatures = []; } /** @@ -774,41 +752,18 @@ function User(userPacket) { User.prototype.toPacketlist = function() { const packetlist = new packet.List(); packetlist.push(this.userId || this.userAttribute); - packetlist.concat(this.revocationCertifications); + packetlist.concat(this.revocationSignatures); packetlist.concat(this.selfCertifications); packetlist.concat(this.otherCertifications); return packetlist; }; -/** - * Checks if a self certificate of the user is revoked - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {module:packet/signature} certificate The certificate to verify - * @param {module:packet/public_subkey|module:packet/public_key| - * module:packet/secret_subkey|module:packet/secret_key} key, optional The key to verify the signature - * @returns {Promise} True if the certificate is revoked - */ -User.prototype.isRevoked = async function(primaryKey, certificate, key) { - certificate.revoked = null; - if (this.revocationCertifications) { - const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; - // TODO clarify OpenPGP's behavior given an expired revocation signature - const results = await Promise.all(this.revocationCertifications.map(async function(revCert) { - return revCert.issuerKeyId.equals(certificate.issuerKeyId) && - !revCert.isExpired() && - (revCert.verified || revCert.verify(key || primaryKey, dataToVerify)); - })); - certificate.revoked = results.some(result => result === true); - return certificate.revoked; - } - return false; -}; - /** * Signs user - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {Array} privateKeys decrypted private keys for signing - * @returns {Promise} new user with new certificate signatures + * @param {module:packet/secret_key| + * module:packet/public_key} primaryKey The primary key packet + * @param {Array} privateKeys Decrypted private keys for signing + * @returns {Promise} New user with new certificate signatures */ User.prototype.sign = async function(primaryKey, privateKeys) { const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey }; @@ -820,7 +775,7 @@ User.prototype.sign = async function(primaryKey, privateKeys) { if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) { throw new Error('Not implemented for self signing'); } - await privateKey.verifyPrimaryUser(); + await privateKey.verifyKeyPackets(); const signingKeyPacket = privateKey.getSigningKeyPacket(); if (!signingKeyPacket) { throw new Error(`Could not find valid signing key packet in key ${ @@ -843,13 +798,35 @@ User.prototype.sign = async function(primaryKey, privateKeys) { return user; }; +/** + * Checks if a given certificate of the user is revoked + * @param {module:packet/secret_key| + * module:packet/public_key} primaryKey The primary key packet + * @param {module:packet/signature} certificate The certificate to verify + * @param {module:packet/public_subkey| + * module:packet/secret_subkey| + * module:packet/public_key| + * module:packet/secret_key} key, optional The key to verify the signature + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} True if the certificate is revoked + */ +User.prototype.isRevoked = async function(primaryKey, certificate, key, date=new Date()) { + return isDataRevoked( + primaryKey, { + key: primaryKey, + userid: this.userId || this.userAttribute + }, this.revocationSignatures, certificate, key, date + ); +}; + /** * Verifies the user certificate - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet + * @param {module:packet/secret_key| + module:packet/public_key} primaryKey The primary key packet * @param {module:packet/signature} certificate A certificate of this user - * @param {Array} keys array of keys to verify certificate signatures - * @param {Date} date use the given date for verification instead of the current time - * @returns {Promise} status of the certificate + * @param {Array} keys Array of keys to verify certificate signatures + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} status of the certificate */ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date=new Date()) { const that = this; @@ -857,7 +834,7 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const results = await Promise.all(keys.map(async function(key) { if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } - await key.verifyPrimaryUser(); + await key.verifyKeyPackets(); const keyPacket = key.getSigningKeyPacket(keyid, date); if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) { return enums.keyStatus.revoked; @@ -875,13 +852,15 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, /** * Verifies all user certificates - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {Array} keys array of keys to verify certificate signatures - * @returns {Promise>} list of signer's keyid and validity of signature + * @param {module:packet/secret_key| + * module:packet/public_key} primaryKey The primary key packet + * @param {Array} keys Array of keys to verify certificate signatures + * @returns {Promise>} List of signer's keyid and validity of signature */ User.prototype.verifyAllCertifications = async function(primaryKey, keys) { const that = this; - const certifications = this.selfCertifications.concat(this.otherCertifications || []); + const certifications = this.selfCertifications.concat(this.otherCertifications); return Promise.all(certifications.map(async function(certification) { const status = await that.verifyCertificate(primaryKey, certification, keys); return { @@ -894,28 +873,30 @@ User.prototype.verifyAllCertifications = async function(primaryKey, keys) { /** * Verify User. Checks for existence of self signatures, revocation signatures * and validity of self signature - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @returns {Promise} status of user + * @param {module:packet/secret_key| + * module:packet/public_key} primaryKey The primary key packet + * @returns {Promise} Status of user */ User.prototype.verify = async function(primaryKey) { - if (!this.selfCertifications) { + if (!this.selfCertifications.length) { return enums.keyStatus.no_self_cert; } const that = this; const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; // TODO replace when Promise.some or Promise.any are implemented - const results = [enums.keyStatus.invalid].concat(await Promise.all(this.selfCertifications.map(async function(selfCertification) { - if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification)) { - return enums.keyStatus.revoked; - } - if (!(selfCertification.verified || await selfCertification.verify(primaryKey, dataToVerify))) { - return enums.keyStatus.invalid; - } - if (selfCertification.isExpired()) { - return enums.keyStatus.expired; - } - return enums.keyStatus.valid; - }))); + const results = [enums.keyStatus.invalid].concat( + await Promise.all(this.selfCertifications.map(async function(selfCertification) { + if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification)) { + return enums.keyStatus.revoked; + } + if (!(selfCertification.verified || await selfCertification.verify(primaryKey, dataToVerify))) { + return enums.keyStatus.invalid; + } + if (selfCertification.isExpired()) { + return enums.keyStatus.expired; + } + return enums.keyStatus.valid; + }))); return results.some(status => status === enums.keyStatus.valid) ? enums.keyStatus.valid : results.pop(); }; @@ -923,7 +904,7 @@ User.prototype.verify = async function(primaryKey) { /** * Update user with new components from specified user * @param {module:key~User} user source user to merge - * @param {Promise} primaryKey primary key used for validation + * @param {module:packet/signature} primaryKey primary key used for validation */ User.prototype.update = async function(user, primaryKey) { const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; @@ -934,7 +915,9 @@ User.prototype.update = async function(user, primaryKey) { // other signatures await mergeSignatures(user, this, 'otherCertifications'); // revocation signatures - await mergeSignatures(user, this, 'revocationCertifications'); + await mergeSignatures(user, this, 'revocationSignatures', function(srcRevSig) { + return isDataRevoked(primaryKey, dataToVerify, [srcRevSig]); + }); }; /** @@ -947,7 +930,7 @@ function SubKey(subKeyPacket) { } this.subKey = subKeyPacket; this.bindingSignatures = []; - this.revocationSignature = null; + this.revocationSignatures = []; } /** @@ -957,65 +940,43 @@ function SubKey(subKeyPacket) { SubKey.prototype.toPacketlist = function() { const packetlist = new packet.List(); packetlist.push(this.subKey); - packetlist.push(this.revocationSignature); - for (let i = 0; i < this.bindingSignatures.length; i++) { - packetlist.push(this.bindingSignatures[i]); - } + packetlist.concat(this.revocationSignatures); + packetlist.concat(this.bindingSignatures); return packetlist; }; /** - * Returns true if the subkey can be used for encryption - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {Date} date use the given date for verification instead of the current time - * @returns {Promise} + * Checks if a binding signature of a subkey is revoked + * @param {module:packet/secret_key| + * module:packet/public_key} primaryKey The primary key packet + * @param {module:packet/signature} signature The binding signature to verify + * @param {module:packet/public_subkey| + * module:packet/secret_subkey| + * module:packet/public_key| + * module:packet/secret_key} key, optional The key to verify the signature + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} True if the binding signature is revoked */ -SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) { - if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { - return false; - } - for (let i = 0; i < this.bindingSignatures.length; i++) { - if (isValidEncryptionKeyPacket(this.subKey, this.bindingSignatures[i], date)) { - return true; - } - } - return false; -}; - -/** - * Returns true if the subkey can be used for signing of data - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {Date} date use the given date for verification instead of the current time - * @returns {Promise} - */ -SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) { - if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { - return false; - } - for (let i = 0; i < this.bindingSignatures.length; i++) { - if (isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i], date)) { - return true; - } - } - return false; +SubKey.prototype.isRevoked = async function(primaryKey, signature, key, date=new Date()) { + return isDataRevoked( + primaryKey, { + key: primaryKey, + bind: this.subKey + }, this.revocationSignatures, signature, key, date + ); }; /** * Verify subkey. Checks for revocation signatures, expiration time * and valid binding signature - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {Date} date use the given date for verification instead of the current time - * @returns {Promise} The status of the subkey + * @param {module:packet/secret_key| + * module:packet/public_key} primaryKey The primary key packet + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} The status of the subkey */ SubKey.prototype.verify = async function(primaryKey, date=new Date()) { const that = this; - // TODO clarify OpenPGP's behavior given an expired revocation signature - // check subkey revocation signature - if (this.revocationSignature && !this.revocationSignature.isExpired() && - (this.revocationSignature.verified || - await this.revocationSignature.verify(primaryKey, { key: primaryKey, bind: this.subKey }))) { - return enums.keyStatus.revoked; - } + const dataToVerify = { key: primaryKey, bind: this.subKey }; const creationTime = this.subKey.created.getTime(); const currentTime = util.normalizeDate(date); // check V3 expiration time @@ -1027,25 +988,31 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) { } // check subkey binding signatures (at least one valid binding sig needed) // TODO replace when Promise.some or Promise.any are implemented - const results = [enums.keyStatus.invalid].concat(await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { - // check binding signature is not expired - if (bindingSignature.isExpired(date)) { - return enums.keyStatus.expired; // last expired binding signature - } - // check binding signature can verify - if (!(bindingSignature.verified || - await bindingSignature.verify(primaryKey, { key: primaryKey, bind: that.subKey }))) { - return enums.keyStatus.invalid; // last invalid binding signature - } - // check V4 expiration time - if (that.subKey.version === 4 && currentTime !== null) { - const expirationTime = bindingSignature.keyNeverExpires === false ? (creationTime + bindingSignature.keyExpirationTime*1000) : Infinity; - if (!(creationTime <= currentTime && currentTime < expirationTime)) { - return enums.keyStatus.expired; // last V4 expired binding signature + const results = [enums.keyStatus.invalid].concat( + await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { + // check binding signature is verified + if (!(bindingSignature.verified || await bindingSignature.verify(primaryKey, dataToVerify))) { + return enums.keyStatus.invalid; } - } - return enums.keyStatus.valid; // found a binding signature that passed all checks - }))); + // check binding signature is not revoked + if (bindingSignature.revoked || await that.isRevoked(primaryKey, bindingSignature, null, date)) { + return enums.keyStatus.revoked; + } + // check binding signature is not expired + if (bindingSignature.isExpired(date)) { + return enums.keyStatus.expired; + } + // check V4 expiration time + if (that.subKey.version === 4 && currentTime !== null) { + const expirationTime = bindingSignature.keyNeverExpires === false ? + (creationTime + bindingSignature.keyExpirationTime*1000) : Infinity; + if (!(creationTime <= currentTime && currentTime < expirationTime)) { + return enums.keyStatus.expired; // last V4 expired binding signature + } + } + return enums.keyStatus.valid; // found a binding signature that passed all checks + })) + ); return results.some(status => status === enums.keyStatus.valid) ? enums.keyStatus.valid : results.pop(); }; @@ -1071,7 +1038,7 @@ SubKey.prototype.getExpirationTime = function() { /** * Update subkey with new components from specified subkey * @param {module:key~SubKey} subKey source subkey to merge - * @param {Promise} primaryKey primary key used for validation + * @param {module:packet/signature} primaryKey primary key used for validation */ SubKey.prototype.update = async function(subKey, primaryKey) { if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { @@ -1087,27 +1054,24 @@ SubKey.prototype.update = async function(subKey, primaryKey) { } // update missing binding signatures const that = this; - await Promise.all(subKey.bindingSignatures.map(async function(newBindingSignature) { - if (newBindingSignature.verified || - await newBindingSignature.verify(primaryKey, { key: primaryKey, bind: that.subKey })) { - for (let i = 0; i < that.bindingSignatures.length; i++) { - if (that.bindingSignatures[i].issuerKeyId.equals(newBindingSignature.issuerKeyId)) { - that.bindingSignatures[i] = newBindingSignature; - return; - } - } - that.bindingSignatures.push(newBindingSignature); + const dataToVerify = { key: primaryKey, bind: that.subKey }; + await mergeSignatures(subKey, this, 'bindingSignatures', async function(srcBindSig) { + if (!(srcBindSig.verified || await srcBindSig.verify(primaryKey, dataToVerify))) { + return false; } - })); - // TODO clarify OpenPGP's behavior given an expired revocation signature - // revocation signature - if (!this.revocationSignature && - subKey.revocationSignature && - !subKey.revocationSignature.isExpired() && - (subKey.revocationSignature.verified || - await subKey.revocationSignature.verify(primaryKey, { key: primaryKey, bind: this.subKey }))) { - this.revocationSignature = subKey.revocationSignature; - } + for (let i = 0; i < that.bindingSignatures.length; i++) { + if (that.bindingSignatures[i].issuerKeyId.equals(srcBindSig.issuerKeyId)) { + // TODO check which one is more recent + that.bindingSignatures[i] = srcBindSig; + return false; + } + } + return true; + }); + // revocation signatures + await mergeSignatures(subKey, this, 'revocationSignatures', function(srcRevSig) { + return isDataRevoked(primaryKey, dataToVerify, [srcRevSig]); + }); }; /** @@ -1146,7 +1110,8 @@ export function read(data) { /** * Reads an OpenPGP armored text and returns one or multiple key objects * @param {String} armoredText text to be parsed - * @returns {{keys: Array, err: (Array|null)}} result object with key and error arrays + * @returns {{keys: Array, + err: (Array|null)}} result object with key and error arrays * @static */ export function readArmored(armoredText) { @@ -1255,16 +1220,13 @@ export function generate(options) { export async function reformat(options) { let secretKeyPacket; let secretSubkeyPacket; - options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; - if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated + // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated + if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { throw new Error('Only RSA Encrypt or Sign supported'); } - try { - await options.privateKey.decrypt(); - } - catch(err) { + if (!options.privateKey.decrypt()) { throw new Error('Key not decrypted'); } @@ -1295,7 +1257,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { if (options.passphrase) { await secretKeyPacket.encrypt(options.passphrase); if (secretSubkeyPacket) { - secretSubkeyPacket.encrypt(options.passphrase); + await secretSubkeyPacket.encrypt(options.passphrase); } } @@ -1380,6 +1342,43 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { return new Key(packetlist); } +/** + * Checks if a given certificate or binding signature is revoked + * @param {module:packet/secret_key| + * module:packet/public_key} primaryKey The primary key packet + * @param {Object} dataToVerify The data to check + * @param {Array} revocations The revocation signatures to check + * @param {module:packet/signature} signature The certificate or signature to check + * @param {module:packet/public_subkey| + * module:packet/secret_subkey| + * module:packet/public_key| + * module:packet/secret_key} key, optional The key packet to check the signature + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} True if the signature revokes the data + */ +async function isDataRevoked(primaryKey, dataToVerify, revocations, signature, key, date=new Date()) { + key = key || primaryKey; + const normDate = util.normalizeDate(date); + const revocationKeyIds = []; + await Promise.all(revocations.map(async function(revocationSignature) { + if (!(config.revocations_expire && revocationSignature.isExpired(normDate)) && + (revocationSignature.verified || await revocationSignature.verify(key, dataToVerify))) { + // TODO get an identifier of the revoked object instead + revocationKeyIds.push(revocationSignature.issuerKeyId); + return true; + } + return false; + })); + // TODO further verify that this is the signature that should be revoked + // In particular, if signature.issuerKeyId is a wildcard, any revocation signature will revoke it + if (signature) { + signature.revoked = revocationKeyIds.some(keyId => keyId.equals(signature.issuerKeyId)) ? true : + signature.revoked; + return signature.revoked; + } + return revocationKeyIds.length > 0; +} + /** * Returns the preferred signature hash algorithm of a key * @param {object} key @@ -1388,7 +1387,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { export function getPreferredHashAlgo(key) { let hash_algo = config.prefer_hash_algorithm; let pref_algo = hash_algo; - if (Key.prototype.isPrototypeOf(key)) { + if (key instanceof Key) { const primaryUser = key.getPrimaryUser(); if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) { [pref_algo] = primaryUser.selfCertificate.preferredHashAlgorithms; diff --git a/src/message.js b/src/message.js index 2c39b1e1..af6344b0 100644 --- a/src/message.js +++ b/src/message.js @@ -148,24 +148,23 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { if (!symESKeyPacketlist) { throw new Error('No symmetrically encrypted session key packet found.'); } - await Promise.all(symESKeyPacketlist.map(async function(packet) { + await Promise.all(symESKeyPacketlist.map(async function(keyPacket) { await Promise.all(passwords.map(async function(password) { try { - await packet.decrypt(password); - keyPackets.push(packet); + await keyPacket.decrypt(password); + keyPackets.push(keyPacket); } catch (err) {} })); })); - } else if (privateKeys) { const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); if (!pkESKeyPacketlist) { throw new Error('No public key encrypted session key packet found.'); } - await Promise.all(pkESKeyPacketlist.map(async function(packet) { + await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) { const privateKeyPackets = privateKeys.reduce(function(acc, privateKey) { - return acc.concat(privateKey.getKeyPackets(packet.publicKeyId)); - }, []); + return acc.concat(privateKey.getKeyPackets(keyPacket.publicKeyId)); + }, new packet.List()); await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { if (!privateKeyPacket) { return; @@ -174,8 +173,8 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { throw new Error('Private key is not decrypted.'); } try { - await packet.decrypt(privateKeyPacket); - keyPackets.push(packet); + await keyPacket.decrypt(privateKeyPacket); + keyPackets.push(keyPacket); } catch (err) {} })); })); @@ -302,7 +301,7 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor if (publicKeys) { const results = await Promise.all(publicKeys.map(async function(key) { - await key.verifyPrimaryUser(); + await key.verifyKeyPackets(undefined, date); const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date); if (!encryptionKeyPacket) { throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); @@ -318,7 +317,6 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor })); packetlist.concat(results); } - if (passwords) { const testDecrypt = async function(keyPacket, password) { try { @@ -396,7 +394,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } - await privateKey.verifyPrimaryUser(); + await privateKey.verifyKeyPackets(undefined, date); const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + @@ -475,10 +473,11 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } - await privateKey.verifyPrimaryUser(); + await privateKey.verifyKeyPackets(undefined, date); const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); if (!signingKeyPacket) { - throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); + throw new Error('Could not find valid key packet for signing in key ' + + privateKey.primaryKey.getKeyId().toHex()); } if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); @@ -545,15 +544,14 @@ export async function createVerificationObjects(signatureList, literalDataList, return Promise.all(signatureList.map(async function(signature) { let keyPacket = null; await Promise.all(keys.map(async function(key) { - await key.verifyPrimaryUser(); // Look for the unique key packet that matches issuerKeyId of signature + await key.verifyKeyPackets(signature.issuerKeyId, date); const result = key.getSigningKeyPacket(signature.issuerKeyId, date); if (result) { keyPacket = result; } })); - // Look for the unique key packet that matches issuerKeyId of signature const verifiedSig = { keyid: signature.issuerKeyId, valid: keyPacket ? await signature.verify(keyPacket, literalDataList[0]) : null diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js index 35c561c7..0e5438ea 100644 --- a/src/packet/packetlist.js +++ b/src/packet/packetlist.js @@ -177,6 +177,19 @@ Packetlist.prototype.some = async function (callback) { return false; }; +/** +* Executes the callback function once for each element, +* returns true if all callbacks returns a truthy value +*/ +Packetlist.prototype.every = function (callback) { + for (let i = 0; i < this.length; i++) { + if (!callback(this[i], i, this)) { + return false; + } + } + return true; +}; + /** * Traverses packet tree and returns first matching packet * @param {module:enums.packet} type The packet type @@ -240,6 +253,7 @@ Packetlist.prototype.concat = function (packetlist) { this.push(packetlist[i]); } } + return this; }; /** diff --git a/src/packet/signature.js b/src/packet/signature.js index 4e0fd88d..d2bf41ef 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -86,6 +86,7 @@ export default function Signature(date=new Date()) { this.embeddedSignature = null; this.verified = null; + this.revoked = null; } /** diff --git a/src/type/keyid.js b/src/type/keyid.js index 1b75bb9a..c741bd51 100644 --- a/src/type/keyid.js +++ b/src/type/keyid.js @@ -53,7 +53,7 @@ Keyid.prototype.toHex = function() { }; Keyid.prototype.equals = function(keyid) { - return this.bytes === keyid.bytes; + return keyid.isWildcard() || this.bytes === keyid.bytes; }; Keyid.prototype.isNull = function() { diff --git a/test/general/key.js b/test/general/key.js index 87d53190..a241e64f 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1278,10 +1278,11 @@ describe('Key', function() { }); }); - it('Find a valid subkey binding signature among many invalid ones', function(done) { + it('Find a valid subkey binding signature among many invalid ones', function() { const k = openpgp.key.readArmored(valid_binding_sig_among_many_expired_sigs_pub).keys[0]; - expect(k.getEncryptionKeyPacket()).to.not.be.null; - done(); + return k.verifyKeyPackets().then(() => { + expect(k.getEncryptionKeyPacket()).to.not.be.null; + }) }); }); diff --git a/test/general/signature.js b/test/general/signature.js index 303a28e1..166cae47 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -448,8 +448,7 @@ describe("Signature", function() { const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - const keyids = esMsg.getEncryptionKeyIds(); - privKey.decryptKeyPacket(keyids, 'hello world'); + esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId)); return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) { expect(decrypted.data).to.exist; @@ -483,8 +482,7 @@ describe("Signature", function() { const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - const keyids = esMsg.getEncryptionKeyIds(); - privKey.decryptKeyPacket(keyids, 'hello world'); + esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId)); return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) { expect(decrypted.data).to.exist; From 73a240df6cff7c921edd68577a65742ad1a5b9a0 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Mon, 5 Mar 2018 19:51:30 -0800 Subject: [PATCH 2/6] Simplifies (Key|User|SubKey).isRevoked, API changes in key.js For User s/revocationCertifications/revocationSignatures/g For Key/SubKey s/revocationSignature/revocationSignatures/g is now an array. --- src/cleartext.js | 3 +- src/key.js | 20 +++++++--- src/keyring/keyring.js | 2 +- src/message.js | 13 ++++--- src/openpgp.js | 10 +++-- src/packet/secret_key.js | 2 +- src/packet/sym_encrypted_session_key.js | 4 +- src/type/keyid.js | 1 + test/general/ecc_nist.js | 1 - test/general/key.js | 41 ++++++++++---------- test/general/packet.js | 50 ++++++++++++------------- test/general/signature.js | 10 +++-- test/general/x25519.js | 2 - 13 files changed, 85 insertions(+), 74 deletions(-) diff --git a/src/cleartext.js b/src/cleartext.js index 36bf6f6b..a794319a 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -155,8 +155,7 @@ export function readArmored(armoredText) { packetlist.read(input.data); verifyHeaders(input.headers, packetlist); const signature = new Signature(packetlist); - const newMessage = new CleartextMessage(input.text, signature); - return newMessage; + return new CleartextMessage(input.text, signature); } /** diff --git a/src/key.js b/src/key.js index 4b045d33..a54e61ed 100644 --- a/src/key.js +++ b/src/key.js @@ -529,9 +529,10 @@ Key.prototype.getPrimaryUser = function(date=new Date()) { * Update key with new components from specified key with same key ID: * users, subkeys, certificates are merged into the destination key, * duplicates and expired signatures are ignored. + * * If the specified key is a private key and the destination key is public, * the destination key is transformed to a private key. - * @param {module:key~Key} key source key to merge + * @param {module:key~Key} key Source key to merge */ Key.prototype.update = async function(key) { const that = this; @@ -903,8 +904,9 @@ User.prototype.verify = async function(primaryKey) { /** * Update user with new components from specified user - * @param {module:key~User} user source user to merge - * @param {module:packet/signature} primaryKey primary key used for validation + * @param {module:key~User} user Source user to merge + * @param {module:packet/secret_key| + module:packet/secret_subkey} primaryKey primary key used for validation */ User.prototype.update = async function(user, primaryKey) { const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; @@ -1037,8 +1039,9 @@ SubKey.prototype.getExpirationTime = function() { /** * Update subkey with new components from specified subkey - * @param {module:key~SubKey} subKey source subkey to merge - * @param {module:packet/signature} primaryKey primary key used for validation + * @param {module:key~SubKey} subKey Source subkey to merge + * @param {module:packet/secret_key| + module:packet/secret_subkey} primaryKey primary key used for validation */ SubKey.prototype.update = async function(subKey, primaryKey) { if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { @@ -1226,7 +1229,12 @@ export async function reformat(options) { throw new Error('Only RSA Encrypt or Sign supported'); } - if (!options.privateKey.decrypt()) { + try { + const isDecrypted = options.privateKey.getKeyPackets().every(keyPacket => keyPacket.isDecrypted); + if (!isDecrypted) { + await options.privateKey.decrypt(); + } + } catch (err) { throw new Error('Key not decrypted'); } diff --git a/src/keyring/keyring.js b/src/keyring/keyring.js index 32d09738..4bf39e4e 100644 --- a/src/keyring/keyring.js +++ b/src/keyring/keyring.js @@ -161,7 +161,7 @@ KeyArray.prototype.getForId = function (keyId, deep) { if (keyIdCheck(keyId, this.keys[i].primaryKey)) { return this.keys[i]; } - if (deep && this.keys[i].subKeys) { + if (deep && this.keys[i].subKeys.length) { for (let j = 0; j < this.keys[i].subKeys.length; j++) { if (keyIdCheck(keyId, this.keys[i].subKeys[j].subKey)) { return this.keys[i]; diff --git a/src/message.js b/src/message.js index af6344b0..6293859f 100644 --- a/src/message.js +++ b/src/message.js @@ -138,7 +138,8 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) * Decrypt encrypted session keys either with private keys or passwords. * @param {Array} privateKeys (optional) private keys with decrypted secret data * @param {Array} passwords (optional) passwords used to decrypt - * @returns {Promise{Array<{ data:Uint8Array, algorithm:String }>}} array of object with potential sessionKey, algorithm pairs + * @returns {Promise>} array of object with potential sessionKey, algorithm pairs */ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { let keyPackets = []; @@ -162,6 +163,7 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { throw new Error('No public key encrypted session key packet found.'); } await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) { + // TODO improve this const privateKeyPackets = privateKeys.reduce(function(acc, privateKey) { return acc.concat(privateKey.getKeyPackets(keyPacket.publicKeyId)); }, new packet.List()); @@ -534,11 +536,12 @@ Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { /** * Create list of objects containing signer's keyid and validity of signature - * @param {Array} signatureList array of signature packets - * @param {Array} literalDataList array of literal data packets - * @param {Array} keys array of keys to verify signatures + * @param {Array} signatureList array of signature packets + * @param {Array} literalDataList array of literal data packets + * @param {Array} keys array of keys to verify signatures * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time - * @returns {Promise{Array<({keyid: module:type/keyid, valid: Boolean})>}} list of signer's keyid and validity of signature + * @returns {Promise>} list of signer's keyid and validity of signature */ export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date()) { return Promise.all(signatureList.map(async function(signature) { diff --git a/src/openpgp.js b/src/openpgp.js index 4f73229a..201b4d64 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -371,8 +371,10 @@ export function verify({ message, publicKeys, signature=null, date=new Date() }) return Promise.resolve().then(async function() { const result = {}; - result.data = CleartextMessage.prototype.isPrototypeOf(message) ? message.getText() : message.getLiteralData(); - result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date) : await message.verify(publicKeys, date); + result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData(); + result.signatures = signature ? + await message.verifyDetached(signature, publicKeys, date) : + await message.verify(publicKeys, date); return result; }).catch(onError.bind(null, 'Error verifying cleartext signed message')); } @@ -462,12 +464,12 @@ function checkData(data, name) { } } function checkMessage(message) { - if (!messageLib.Message.prototype.isPrototypeOf(message)) { + if (!(message instanceof messageLib.Message)) { throw new Error('Parameter [message] needs to be of type Message'); } } function checkCleartextOrMessage(message) { - if (!CleartextMessage.prototype.isPrototypeOf(message) && !messageLib.Message.prototype.isPrototypeOf(message)) { + if (!(message instanceof CleartextMessage) && !(message instanceof messageLib.Message)) { throw new Error('Parameter [message] needs to be of type Message or CleartextMessage'); } } diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 13fd624d..f23343fe 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -216,7 +216,7 @@ function produceEncryptionKey(s2k, passphrase, algorithm) { */ SecretKey.prototype.decrypt = async function (passphrase) { if (this.isDecrypted) { - return true; + throw new Error('Key packet is already decrypted.'); } let i = 0; diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index 3884f072..6433b0ab 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -104,7 +104,7 @@ SymEncryptedSessionKey.prototype.write = function() { /** * Decrypts the session key * @param {String} passphrase The passphrase in string form - * @return {Promise} */ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { const algo = this.sessionKeyEncryptionAlgorithm !== null ? @@ -128,7 +128,7 @@ SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { /** * Encrypts the session key * @param {String} passphrase The passphrase in string form - * @return {Promise} */ SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) { const algo = this.sessionKeyEncryptionAlgorithm !== null ? diff --git a/src/type/keyid.js b/src/type/keyid.js index c741bd51..16fd3324 100644 --- a/src/type/keyid.js +++ b/src/type/keyid.js @@ -53,6 +53,7 @@ Keyid.prototype.toHex = function() { }; Keyid.prototype.equals = function(keyid) { + // Note: checks if keyid is a wildcard, but doesn't check "this". return keyid.isWildcard() || this.bytes === keyid.bytes; }; diff --git a/test/general/ecc_nist.js b/test/general/ecc_nist.js index f88708d8..264e07cd 100644 --- a/test/general/ecc_nist.js +++ b/test/general/ecc_nist.js @@ -202,7 +202,6 @@ describe('Elliptic Curve Cryptography', function () { it('Encrypt and sign message', async function () { const romeoPrivate = await load_priv_key('romeo'); const julietPublic = load_pub_key('juliet'); - expect(await romeoPrivate.decrypt(data.romeo.pass)).to.be.true; const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], data: data.romeo.message + "\n"}); const message = openpgp.message.readArmored(encrypted.data); diff --git a/test/general/key.js b/test/general/key.js index a241e64f..dff1d7ed 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -769,7 +769,7 @@ describe('Key', function() { const pubKey = pubKeys.keys[0]; // remove subkeys - pubKey.subKeys = null; + pubKey.subKeys = []; // primary key has only key flags for signing const keyPacket = pubKey.getEncryptionKeyPacket(); expect(keyPacket).to.not.exist; @@ -798,13 +798,13 @@ describe('Key', function() { )()).to.be.rejectedWith('Key update method: fingerprints of keys not equal').notify(done); }); - it('update() - merge revocation signature', function(done) { + it('update() - merge revocation signatures', function(done) { const source = openpgp.key.readArmored(pub_revoked).keys[0]; const dest = openpgp.key.readArmored(pub_revoked).keys[0]; - expect(source.revocationSignature).to.exist; - dest.revocationSignature = null; + expect(source.revocationSignatures).to.exist; + dest.revocationSignatures = []; dest.update(source).then(() => { - expect(dest.revocationSignature).to.exist.and.be.an.instanceof(openpgp.packet.Signature); + expect(dest.revocationSignatures[0]).to.exist.and.be.an.instanceof(openpgp.packet.Signature); done(); }); }); @@ -821,18 +821,18 @@ describe('Key', function() { }); }); - it('update() - merge user - other and revocation certification', function(done) { + it('update() - merge user - other and certification revocation signatures', function(done) { const source = openpgp.key.readArmored(pub_sig_test).keys[0]; const dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.users[1].otherCertifications).to.exist; - expect(source.users[1].revocationCertifications).to.exist; - dest.users[1].otherCertifications = null; - dest.users[1].revocationCertifications.pop(); + expect(source.users[1].revocationSignatures).to.exist; + dest.users[1].otherCertifications = []; + dest.users[1].revocationSignatures.pop(); dest.update(source).then(() => { expect(dest.users[1].otherCertifications).to.exist.and.to.have.length(1); expect(dest.users[1].otherCertifications[0].signature).to.equal(source.users[1].otherCertifications[0].signature); - expect(dest.users[1].revocationCertifications).to.exist.and.to.have.length(2); - expect(dest.users[1].revocationCertifications[1].signature).to.equal(source.users[1].revocationCertifications[1].signature); + expect(dest.users[1].revocationSignatures).to.exist.and.to.have.length(2); + expect(dest.users[1].revocationSignatures[1].signature).to.equal(source.users[1].revocationSignatures[1].signature); done(); }); }); @@ -851,15 +851,14 @@ describe('Key', function() { }); }); - it('update() - merge subkey - revocation signature', function(done) { + it('update() - merge subkey - revocation signature', function() { const source = openpgp.key.readArmored(pub_sig_test).keys[0]; const dest = openpgp.key.readArmored(pub_sig_test).keys[0]; - expect(source.subKeys[0].revocationSignature).to.exist; - dest.subKeys[0].revocationSignature = null; - dest.update(source).then(() => { - expect(dest.subKeys[0].revocationSignature).to.exist; - expect(dest.subKeys[0].revocationSignature.signature).to.equal(dest.subKeys[0].revocationSignature.signature); - done(); + expect(source.subKeys[0].revocationSignatures).to.exist; + dest.subKeys[0].revocationSignatures = []; + return dest.update(source).then(() => { + expect(dest.subKeys[0].revocationSignatures).to.exist; + expect(dest.subKeys[0].revocationSignatures[0].signature).to.equal(dest.subKeys[0].revocationSignatures[0].signature); }); }); @@ -886,8 +885,8 @@ describe('Key', function() { it('update() - merge private key into public key - no subkeys', function() { const source = openpgp.key.readArmored(priv_key_rsa).keys[0]; const dest = openpgp.key.readArmored(twoKeys).keys[0]; - source.subKeys = null; - dest.subKeys = null; + source.subKeys = []; + dest.subKeys = []; expect(dest.isPublic()).to.be.true; return dest.update(source).then(() => { expect(dest.isPrivate()).to.be.true; @@ -905,7 +904,7 @@ describe('Key', function() { it('update() - merge private key into public key - mismatch throws error', function(done) { const source = openpgp.key.readArmored(priv_key_rsa).keys[0]; const dest = openpgp.key.readArmored(twoKeys).keys[0]; - source.subKeys = null; + source.subKeys = []; expect(dest.subKeys).to.exist; expect(dest.isPublic()).to.be.true; expect(dest.update.bind(dest, source)()) diff --git a/test/general/packet.js b/test/general/packet.js index c5fe98d4..1c07bd76 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -141,7 +141,7 @@ describe("Packet", function() { }); }); - it('Sym encrypted session key with a compressed packet', function(done) { + it('Sym encrypted session key with a compressed packet', async function() { const msg = '-----BEGIN PGP MESSAGE-----\n' + 'Version: GnuPG v2.0.19 (GNU/Linux)\n' + @@ -156,16 +156,16 @@ describe("Packet", function() { const parsed = new openpgp.packet.List(); parsed.read(msgbytes); - parsed[0].decrypt('test'); + return parsed[0].decrypt('test').then(() => { + const key = parsed[0].sessionKey; + return parsed[1].decrypt(parsed[0].sessionKeyAlgorithm, key).then(() => { + const compressed = parsed[1].packets[0]; - const key = parsed[0].sessionKey; - parsed[1].decrypt(parsed[0].sessionKeyAlgorithm, key); - const compressed = parsed[1].packets[0]; + const result = stringify(compressed.packets[0].data); - const result = stringify(compressed.packets[0].data); - - expect(result).to.equal('Hello world!\n'); - done(); + expect(result).to.equal('Hello world!\n'); + }); + }); }); it('Public key encrypted symmetric key packet', function() { @@ -187,13 +187,13 @@ describe("Packet", function() { enc.publicKeyAlgorithm = 'rsa_encrypt'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - enc.encrypt({ params: mpi }).then(() => { + return enc.encrypt({ params: mpi }).then(() => { msg.push(enc); msg2.read(msg.write()); - msg2[0].decrypt({ params: mpi }).then(() => { + return msg2[0].decrypt({ params: mpi }).then(() => { expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); @@ -299,8 +299,8 @@ describe("Packet", function() { const msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - return msg[0].decrypt(key).then(() => { - return msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + return msg[0].decrypt(key).then(async () => { + await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); const text = stringify(msg[1].packets[0].packets[0].data); @@ -339,7 +339,7 @@ describe("Packet", function() { expect(stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); }); - it('Secret key encryption/decryption test', function() { + it('Secret key encryption/decryption test', async function() { const armored_msg = '-----BEGIN PGP MESSAGE-----\n' + 'Version: GnuPG v2.0.19 (GNU/Linux)\n' + @@ -355,13 +355,13 @@ describe("Packet", function() { let key = new openpgp.packet.List(); key.read(openpgp.armor.decode(armored_key).data); key = key[3]; - key.decrypt('test'); + await key.decrypt('test'); const msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - return msg[0].decrypt(key).then(() => { - return msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + return msg[0].decrypt(key).then(async () => { + await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); const text = stringify(msg[1].packets[0].packets[0].data); @@ -386,7 +386,7 @@ describe("Packet", function() { ]); }); - it('Reading a signed, encrypted message.', function(done) { + it('Reading a signed, encrypted message.', async function() { const armored_msg = '-----BEGIN PGP MESSAGE-----\n' + 'Version: GnuPG v2.0.19 (GNU/Linux)\n' + @@ -405,19 +405,19 @@ describe("Packet", function() { const key = new openpgp.packet.List(); key.read(openpgp.armor.decode(armored_key).data); - key[3].decrypt('test'); + await key[3].decrypt('test'); const msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - msg[0].decrypt(key[3]).then(() => { - msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + return msg[0].decrypt(key[3]).then(async () => { + await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); const payload = msg[1].packets[0].packets; expect(payload[2].verify( key[0], payload[1] - )).to.eventually.be.true.notify(done); + )).to.eventually.be.true; }); }); @@ -428,7 +428,7 @@ describe("Packet", function() { const rsa = openpgp.crypto.publicKey.rsa; const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys - return rsa.generate(keySize, "10001").then(function(mpiGen) { + return rsa.generate(keySize, "10001").then(async function(mpiGen) { let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; mpi = mpi.map(function(k) { return new openpgp.MPI(k); @@ -436,13 +436,13 @@ describe("Packet", function() { key[0].params = mpi; key[0].algorithm = "rsa_sign"; - key[0].encrypt('hello'); + await key[0].encrypt('hello'); const raw = key.write(); const key2 = new openpgp.packet.List(); key2.read(raw); - key2[0].decrypt('hello'); + await key2[0].decrypt('hello'); expect(key[0].params.toString()).to.equal(key2[0].params.toString()); }); diff --git a/test/general/signature.js b/test/general/signature.js index 166cae47..0728e126 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -688,16 +688,18 @@ describe("Signature", function() { }); - it('Verify primary key revocation signature', function(done) { + // TODO add test with multiple revocation signatures + it('Verify primary key revocation signatures', function(done) { const pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - expect(pubKey.revocationSignature.verify( + expect(pubKey.revocationSignatures[0].verify( pubKey.primaryKey, {key: pubKey.primaryKey} )).to.eventually.be.true.notify(done); }); - it('Verify subkey revocation signature', function(done) { + // TODO add test with multiple revocation signatures + it('Verify subkey revocation signatures', function(done) { const pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - expect(pubKey.subKeys[0].revocationSignature.verify( + expect(pubKey.subKeys[0].revocationSignatures[0].verify( pubKey.primaryKey, {key: pubKey.primaryKey, bind: pubKey.subKeys[0].subKey} )).to.eventually.be.true.notify(done); }); diff --git a/test/general/x25519.js b/test/general/x25519.js index b7849304..1f1e62ae 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -184,7 +184,6 @@ describe('X25519 Cryptography', function () { it('Decrypt and verify message', async function () { const light = load_pub_key('light'); const night = await load_priv_key('night'); - expect(await night.decrypt(data.night.pass)).to.be.true; const msg = openpgp.message.readArmored(data.night.message_encrypted); const result = await openpgp.decrypt({ privateKeys: night, publicKeys: [light], message: msg }); @@ -198,7 +197,6 @@ describe('X25519 Cryptography', function () { it('Encrypt and sign message', async function () { const nightPublic = load_pub_key('night'); const lightPrivate = await load_priv_key('light'); - expect(await lightPrivate.decrypt(data.light.pass)).to.be.true; const encrypted = await openpgp.encrypt({ publicKeys: [nightPublic], privateKeys: [lightPrivate], data: data.light.message + "\n" }); const message = openpgp.message.readArmored(encrypted.data); From 23a4141ce953f95211320e148a1677805f7801cd Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Tue, 6 Mar 2018 13:15:47 -0800 Subject: [PATCH 3/6] Addresses @sanjanarajan's comments --- src/key.js | 16 ++++++++-------- src/message.js | 11 ++++++----- src/type/keyid.js | 10 +++++++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/key.js b/src/key.js index a54e61ed..7c8d7293 100644 --- a/src/key.js +++ b/src/key.js @@ -153,7 +153,7 @@ Key.prototype.toPacketlist = function() { Key.prototype.getSubkeyPackets = function(keyId=null) { const packets = new packet.List(); this.subKeys.forEach(subKey => { - if (!keyId || subKey.subKey.getKeyId().equals(keyId)) { + if (!keyId || subKey.subKey.getKeyId().equals(keyId, true)) { packets.push(subKey.subKey); } }); @@ -168,7 +168,7 @@ Key.prototype.getSubkeyPackets = function(keyId=null) { */ Key.prototype.getKeyPackets = function(keyId=null) { const packets = new packet.List(); - if (!keyId || this.primaryKey.getKeyId().equals(keyId)) { + if (!keyId || this.primaryKey.getKeyId().equals(keyId, true)) { packets.push(this.primaryKey); } packets.concat(this.getSubkeyPackets(keyId)); @@ -288,7 +288,7 @@ Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { } } } - // TODO throw descriptive error + // TODO how to throw descriptive error? return null; }; @@ -334,7 +334,7 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { return this.primaryKey; } - // TODO throw descriptive error + // TODO how to throw descriptive error? return null; }; @@ -1064,9 +1064,10 @@ SubKey.prototype.update = async function(subKey, primaryKey) { } for (let i = 0; i < that.bindingSignatures.length; i++) { if (that.bindingSignatures[i].issuerKeyId.equals(srcBindSig.issuerKeyId)) { - // TODO check which one is more recent - that.bindingSignatures[i] = srcBindSig; - return false; + if (srcBindSig.created < that.bindingSignatures[i].created) { + that.bindingSignatures[i] = srcBindSig; + return false; + } } } return true; @@ -1378,7 +1379,6 @@ async function isDataRevoked(primaryKey, dataToVerify, revocations, signature, k return false; })); // TODO further verify that this is the signature that should be revoked - // In particular, if signature.issuerKeyId is a wildcard, any revocation signature will revoke it if (signature) { signature.revoked = revocationKeyIds.some(keyId => keyId.equals(signature.issuerKeyId)) ? true : signature.revoked; diff --git a/src/message.js b/src/message.js index 6293859f..adb0cc70 100644 --- a/src/message.js +++ b/src/message.js @@ -536,12 +536,13 @@ Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { /** * Create list of objects containing signer's keyid and validity of signature - * @param {Array} signatureList array of signature packets - * @param {Array} literalDataList array of literal data packets - * @param {Array} keys array of keys to verify signatures - * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time + * @param {Array} signatureList array of signature packets + * @param {Array} literalDataList array of literal data packets + * @param {Array} keys array of keys to verify signatures + * @param {Date} date Verify the signature against the given date, + * i.e. check signature creation time < date < expiration time * @returns {Promise>} list of signer's keyid and validity of signature + * valid: Boolean}>>} list of signer's keyid and validity of signature */ export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date()) { return Promise.all(signatureList.map(async function(signature) { diff --git a/src/type/keyid.js b/src/type/keyid.js index 16fd3324..392d821f 100644 --- a/src/type/keyid.js +++ b/src/type/keyid.js @@ -52,9 +52,13 @@ Keyid.prototype.toHex = function() { return util.str_to_hex(this.bytes); }; -Keyid.prototype.equals = function(keyid) { - // Note: checks if keyid is a wildcard, but doesn't check "this". - return keyid.isWildcard() || this.bytes === keyid.bytes; +/** + * Checks equality of Key ID's + * @param {Keyid} keyid + * @param {Boolean} matchWildcard Indicates whether to check if either keyid is a wildcard + */ +Keyid.prototype.equals = function(keyid, matchWildcard=false) { + return (matchWildcard && (keyid.isWildcard() || this.isWildcard())) || this.bytes === keyid.bytes; }; Keyid.prototype.isNull = function() { From 47006069d1a1e1e1bf5c8d4cb9646fb551340580 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Tue, 6 Mar 2018 19:33:00 -0800 Subject: [PATCH 4/6] Added test for encryption with revoked subkey --- src/message.js | 9 +++++---- test/general/key.js | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/message.js b/src/message.js index adb0cc70..dcfe0836 100644 --- a/src/message.js +++ b/src/message.js @@ -302,11 +302,12 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor const packetlist = new packet.List(); if (publicKeys) { - const results = await Promise.all(publicKeys.map(async function(key) { - await key.verifyKeyPackets(undefined, date); - const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date); + const results = await Promise.all(publicKeys.map(async function(publicKey) { + await publicKey.verifyKeyPackets(undefined, date); + const encryptionKeyPacket = publicKey.getEncryptionKeyPacket(undefined, date); if (!encryptionKeyPacket) { - throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); + throw new Error('Could not find valid key packet for encryption in key ' + + publicKey.primaryKey.getKeyId().toHex()); } const pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey(); pkESKeyPacket.publicKeyId = wildcard ? type_keyid.wildcard() : encryptionKeyPacket.getKeyId(); diff --git a/test/general/key.js b/test/general/key.js index dff1d7ed..56be6152 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1283,5 +1283,14 @@ describe('Key', function() { expect(k.getEncryptionKeyPacket()).to.not.be.null; }) }); + + it('Reject encryption with revoked subkey', function() { + const key = openpgp.key.readArmored(pub_revoked).keys[0]; + return openpgp.encrypt({publicKeys: [key], data: 'random data'}).then(() => { + throw new Error('encryptSessionKey should not encrypt with revoked public key'); + }).catch(function(error) { + expect(error.message).to.equal('Error encrypting message: Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); + }); + }); }); From 0b2817ba39e6842857f083aff26d4f0aa12b2653 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Wed, 7 Mar 2018 13:19:48 -0800 Subject: [PATCH 5/6] Last little things become async ... --- src/key.js | 243 ++++++++++++++++++---------------------- src/message.js | 18 ++- src/packet/signature.js | 4 +- test/general/key.js | 241 ++++++++++++++++++--------------------- test/general/openpgp.js | 137 ++++++++++++---------- 5 files changed, 304 insertions(+), 339 deletions(-) diff --git a/src/key.js b/src/key.js index 7c8d7293..71e23c71 100644 --- a/src/key.js +++ b/src/key.js @@ -188,13 +188,9 @@ Key.prototype.getKeyIds = function() { * @returns {Array} array of userids */ Key.prototype.getUserIds = function() { - const userids = []; - for (let i = 0; i < this.users.length; i++) { - if (this.users[i].userId) { - userids.push(util.Uint8Array_to_str(this.users[i].userId.write())); - } - } - return userids; + return this.users.map(user => { + return user.userId ? util.encode_utf8(user.userId.userid) : null; + }).filter(userid => userid !== null); }; /** @@ -254,33 +250,34 @@ Key.prototype.armor = function() { }; function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) { - const normDate = util.normalizeDate(date); return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && - signature.verified && !signature.revoked && !signature.isExpired(normDate) && - (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity))); + signature.verified && !signature.revoked && !signature.isExpired(date) && + !isDataExpired(keyPacket, signature, date); } /** * Returns first key packet or key packet by given keyId that is available for signing and verification - * - * NOTE: call verifyKeyPackets before calling this function. * @param {module:type/keyid} keyId, optional * @param {Date} date use the given date for verification instead of the current time * @returns {(module:packet/secret_subkey| module:packet/secret_key|null)} key packet or null if no signing key has been found */ -Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { - const primaryUser = this.getPrimaryUser(date); - if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { - return this.primaryKey; +Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date()) { + const primaryKey = this.primaryKey; + const primaryUser = await this.getPrimaryUser(date); + if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) && + isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification, date) && + await this.verifyPrimaryKey(date)) { + return primaryKey; } for (let i = 0; i < this.subKeys.length; i++) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + // eslint-disable-next-line no-await-in-loop + await this.subKeys[i].verify(primaryKey, date); for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { if (isValidSigningKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { return this.subKeys[i].subKey; @@ -288,7 +285,6 @@ Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { } } } - // TODO how to throw descriptive error? return null; }; @@ -301,14 +297,12 @@ function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) { (!signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && - signature.verified && !signature.revoked && !signature.isExpired(normDate) && - (normDate === null || (keyPacket.created <= normDate && normDate < getExpirationTime(keyPacket, signature, Infinity))); + signature.verified && !signature.revoked && !signature.isExpired(date) && + !isDataExpired(keyPacket, signature, date); } /** * Returns first key packet or key packet by given keyId that is available for encryption or decryption - * - * NOTE: call verifyKeyPackets before calling this function. * @param {module:type/keyid} keyId, optional * @param {Date} date, optional * @returns {(module:packet/public_subkey| @@ -316,11 +310,14 @@ function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) { * module:packet/secret_key| * module:packet/public_key|null)} key packet or null if no encryption key has been found */ -Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { +Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date()) { + const primaryKey = this.primaryKey; // V4: by convention subkeys are preferred for encryption service // V3: keys MUST NOT have subkeys for (let i = 0; i < this.subKeys.length; i++) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + // eslint-disable-next-line no-await-in-loop + await this.subKeys[i].verify(primaryKey, date); for (let j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], date)) { return this.subKeys[i].subKey; @@ -329,12 +326,12 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { } } // if no valid subkey for encryption, evaluate primary key - const primaryUser = this.getPrimaryUser(date); - if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate, date)) { - return this.primaryKey; + const primaryUser = await this.getPrimaryUser(date); + if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) && + isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification, date) && + await this.verifyPrimaryKey(date)) { + return primaryKey; } - // TODO how to throw descriptive error? return null; }; @@ -423,37 +420,26 @@ Key.prototype.verifyKeyPackets = async function(keyId=null, date=new Date()) { * @returns {Promise} The status of the primary key */ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { + const primaryKey = this.primaryKey; // check for key revocation signatures if (await this.isRevoked(null, null, date)) { return enums.keyStatus.revoked; } - const creationTime = this.primaryKey.created.getTime(); - const currentTime = util.normalizeDate(date); - // check V3 expiration time - if (date !== null && this.primaryKey.version === 3) { - const expirationTimeV3 = creationTime + (this.primaryKey.expirationTimeV3*24*3600*1000 || Infinity); - if (!(creationTime <= currentTime && currentTime < expirationTimeV3)) { - return enums.keyStatus.expired; - } - } // check for at least one self signature. Self signature of user ID not mandatory // See {@link https://tools.ietf.org/html/rfc4880#section-11.1} if (!this.users.some(user => user.userId && user.selfCertifications.length)) { return enums.keyStatus.no_self_cert; } - // check for valid self signature - await this.verifyPrimaryUser(); - const primaryUser = this.getPrimaryUser(date); - if (!primaryUser) { + // check for valid, unrevoked, unexpired self signature + const { user, selfCertification } = await this.getPrimaryUser(date); + if (!user) { return enums.keyStatus.invalid; } - // check V4 expiration time - if (date !== null && this.primaryKey.version === 4) { - const expirationTime = primaryUser.selfCertificate.keyNeverExpires === false ? - creationTime + primaryUser.selfCertificate.keyExpirationTime*1000 : Infinity; - if (!(creationTime <= currentTime && currentTime < expirationTime)) { - return enums.keyStatus.expired; - } + // check for expiration time + const currentTime = util.normalizeDate(date); + if ((primaryKey.version === 3 && isDataExpired(primaryKey, null, date)) || + (primaryKey.version === 4 && isDataExpired(primaryKey, selfCertification, date))) { + return enums.keyStatus.expired; } return enums.keyStatus.valid; }; @@ -462,64 +448,46 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { * Returns the expiration time of the primary key or null if key does not expire * @returns {Date|null} */ -Key.prototype.getExpirationTime = function() { +Key.prototype.getExpirationTime = async function() { if (this.primaryKey.version === 3) { return getExpirationTime(this.primaryKey); } if (this.primaryKey.version === 4) { - const primaryUser = this.getPrimaryUser(); + const primaryUser = await this.getPrimaryUser(); if (!primaryUser) { return null; } - return getExpirationTime(this.primaryKey, primaryUser.selfCertificate); + return getExpirationTime(this.primaryKey, primaryUser.selfCertification); } }; - -function getExpirationTime(keyPacket, selfCertificate, defaultValue=null) { - // check V3 expiration time - if (keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0) { - return new Date(keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000); - } - // check V4 expiration time - if (keyPacket.version === 4 && selfCertificate.keyNeverExpires === false) { - return new Date(keyPacket.created.getTime() + selfCertificate.keyExpirationTime*1000); - } - return defaultValue; -} - /** * Returns primary user and most significant (latest valid) self signature * - if multiple primary users exist, returns the one with the latest self signature * - otherwise, returns the user with the latest self signature - * - * NOTE: call verifyPrimaryUser before calling this function. - * This is because getPrimaryUser isn't async, so it cannot validate and instead - * relies on already validated certificates. * @param {Date} date use the given date for verification instead of the current time - * @returns {{user: Array, selfCertificate: Array}|null} The primary user and the self signature + * @returns {{user: Array, selfCertification: Array}|null} The primary user and the self signature */ -Key.prototype.getPrimaryUser = function(date=new Date()) { +Key.prototype.getPrimaryUser = async function(date=new Date()) { + // FIXME + await this.verifyPrimaryUser(); let primaryUsers = []; - for (let i = 0; i < this.users.length; i++) { - // here we only check the primary user ID, ignoring the primary user attribute - if (!this.users[i].userId || !this.users[i].selfCertifications.length) { - continue; + this.users.forEach((user, i) => { + if (!user.userId) { + return; } - for (let j = 0; j < this.users[i].selfCertifications.length; j++) { + user.selfCertifications.forEach(cert => { // only consider already validated certificates - if (!this.users[i].selfCertifications[j].verified || - this.users[i].selfCertifications[j].revoked || - this.users[i].selfCertifications[j].isExpired(date)) { - continue; + if (!cert.verified || cert.revoked || cert.isExpired(date)) { + return; } - primaryUsers.push({ index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j] }); - } - } + primaryUsers.push({ index: i, user: user, selfCertification: cert }); + }); + }); // sort by primary user flag and signature creation time primaryUsers = primaryUsers.sort(function(a, b) { - const A = a.selfCertificate; - const B = b.selfCertificate; + const A = a.selfCertification; + const B = b.selfCertification; return (B.isPrimaryUserID - A.isPrimaryUserID) || (B.created - A.created); }); return primaryUsers.pop(); @@ -629,8 +597,7 @@ Key.prototype.revoke = function() { * @returns {Promise} new public key with new certificate signature */ Key.prototype.signPrimaryUser = async function(privateKeys) { - await this.verifyPrimaryUser(); - const { index, user } = this.getPrimaryUser() || {}; + const { index, user } = await this.getPrimaryUser() || {}; if (!user) { throw new Error('Could not find primary user'); } @@ -668,34 +635,32 @@ Key.prototype.verifyPrimaryUser = async function(keys) { let lastCreated = null; let lastPrimaryUserID = null; await Promise.all(this.users.map(async function(user) { - // here we verify both the primary user ID or the primary user attribute - if (!(user.userId || user.userAttribute) || !user.selfCertifications.length) { + if (!user.userId && !user.userAttribute) { return; } const dataToVerify = { userid: user.userId || user.userAttribute, key: primaryKey }; // TODO replace when Promise.forEach is implemented for (let i = 0; i < user.selfCertifications.length; i++) { - const selfCertification = user.selfCertifications[i]; + const cert = user.selfCertifications[i]; // skip if certificate is not the most recent - if ((selfCertification.isPrimaryUserID && - selfCertification.isPrimaryUserID < lastPrimaryUserID) || - (!lastPrimaryUserID && selfCertification.created < lastCreated)) { + if ((cert.isPrimaryUserID && cert.isPrimaryUserID < lastPrimaryUserID) || + (!lastPrimaryUserID && cert.created < lastCreated)) { return; } // skip if certificates is invalid, revoked, or expired // eslint-disable-next-line no-await-in-loop - if (!(selfCertification.verified || await selfCertification.verify(primaryKey, dataToVerify))) { + if (!(cert.verified || await cert.verify(primaryKey, dataToVerify))) { return; } // eslint-disable-next-line no-await-in-loop - if (selfCertification.revoked || await user.isRevoked(primaryKey, selfCertification)) { + if (cert.revoked || await user.isRevoked(primaryKey, cert)) { return; } - if (selfCertification.isExpired()) { + if (cert.isExpired()) { return; } - lastPrimaryUserID = selfCertification.isPrimaryUserID; - lastCreated = selfCertification.created; + lastPrimaryUserID = cert.isPrimaryUserID; + lastCreated = cert.created; primaryUsers.push(user); } })); @@ -776,11 +741,10 @@ User.prototype.sign = async function(primaryKey, privateKeys) { if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) { throw new Error('Not implemented for self signing'); } - await privateKey.verifyKeyPackets(); - const signingKeyPacket = privateKey.getSigningKeyPacket(); + const signingKeyPacket = await privateKey.getSigningKeyPacket(); if (!signingKeyPacket) { - throw new Error(`Could not find valid signing key packet in key ${ - privateKey.primaryKey.getKeyId().toHex()}`); + throw new Error('Could not find valid signing key packet in key ' + + privateKey.primaryKey.getKeyId().toHex()); } if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); @@ -790,7 +754,7 @@ User.prototype.sign = async function(primaryKey, privateKeys) { signaturePacket.signatureType = enums.write(enums.signature, enums.signature.cert_generic); signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; - signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); + signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey); signaturePacket.signingKeyId = signingKeyPacket.getKeyId(); signaturePacket.sign(signingKeyPacket, dataToSign); return signaturePacket; @@ -835,8 +799,7 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const results = await Promise.all(keys.map(async function(key) { if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } - await key.verifyKeyPackets(); - const keyPacket = key.getSigningKeyPacket(keyid, date); + const keyPacket = await key.getSigningKeyPacket(keyid, date); if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) { return enums.keyStatus.revoked; } @@ -979,17 +942,12 @@ SubKey.prototype.isRevoked = async function(primaryKey, signature, key, date=new SubKey.prototype.verify = async function(primaryKey, date=new Date()) { const that = this; const dataToVerify = { key: primaryKey, bind: this.subKey }; - const creationTime = this.subKey.created.getTime(); - const currentTime = util.normalizeDate(date); - // check V3 expiration time - if (currentTime !== null && this.subKey.version === 3) { - const expirationTime = creationTime + (this.subKey.expirationTimeV3*24*3600*1000 || Infinity); - if (!(creationTime <= currentTime && currentTime < expirationTime)) { - return enums.keyStatus.expired; - } + // check for V3 expiration time + if (this.subKey.version === 3 && isDataExpired(this.subKey, null, date)) { + return enums.keyStatus.expired; } - // check subkey binding signatures (at least one valid binding sig needed) - // TODO replace when Promise.some or Promise.any are implemented + // check subkey binding signatures + // note: binding signatures can have different keyFlags, so we verify all. const results = [enums.keyStatus.invalid].concat( await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { // check binding signature is verified @@ -1000,18 +958,10 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) { if (bindingSignature.revoked || await that.isRevoked(primaryKey, bindingSignature, null, date)) { return enums.keyStatus.revoked; } - // check binding signature is not expired + // check binding signature is not expired (ie, check for V4 expiration time) if (bindingSignature.isExpired(date)) { return enums.keyStatus.expired; } - // check V4 expiration time - if (that.subKey.version === 4 && currentTime !== null) { - const expirationTime = bindingSignature.keyNeverExpires === false ? - (creationTime + bindingSignature.keyExpirationTime*1000) : Infinity; - if (!(creationTime <= currentTime && currentTime < expirationTime)) { - return enums.keyStatus.expired; // last V4 expired binding signature - } - } return enums.keyStatus.valid; // found a binding signature that passed all checks })) ); @@ -1284,7 +1234,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { const signaturePacket = new packet.Signature(); signaturePacket.signatureType = enums.signature.cert_generic; signaturePacket.publicKeyAlgorithm = options.keyType; - signaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket); + signaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretKeyPacket); signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.preferredSymmetricAlgorithms = []; // prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support) @@ -1329,7 +1279,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { const subkeySignaturePacket = new packet.Signature(); subkeySignaturePacket.signatureType = enums.signature.subkey_binding; subkeySignaturePacket.publicKeyAlgorithm = options.keyType; - subkeySignaturePacket.hashAlgorithm = getPreferredHashAlgo(secretSubkeyPacket); + subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretSubkeyPacket); subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage]; if (options.keyExpirationTime > 0) { subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime; @@ -1387,18 +1337,41 @@ async function isDataRevoked(primaryKey, dataToVerify, revocations, signature, k return revocationKeyIds.length > 0; } +function isDataExpired(keyPacket, signature, date=new Date()) { + const normDate = util.normalizeDate(date); + if (normDate !== null) { + const expirationTime = getExpirationTime(keyPacket, signature); + return !(keyPacket.created <= normDate && normDate < expirationTime) || + (signature && signature.isExpired(date)); + } + return false; +} + +function getExpirationTime(keyPacket, signature) { + let expirationTime; + // check V3 expiration time + if (keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0) { + expirationTime = keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000; + } + // check V4 expiration time + if (keyPacket.version === 4 && signature.keyNeverExpires === false) { + expirationTime = signature.created.getTime() + signature.keyExpirationTime*1000; + } + return expirationTime ? new Date(expirationTime) : Infinity; +} + /** * Returns the preferred signature hash algorithm of a key * @param {object} key * @returns {String} */ -export function getPreferredHashAlgo(key) { +export async function getPreferredHashAlgo(key) { let hash_algo = config.prefer_hash_algorithm; let pref_algo = hash_algo; if (key instanceof Key) { - const primaryUser = key.getPrimaryUser(); - if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) { - [pref_algo] = primaryUser.selfCertificate.preferredHashAlgorithms; + const primaryUser = await key.getPrimaryUser(); + if (primaryUser && primaryUser.selfCertification.preferredHashAlgorithms) { + [pref_algo] = primaryUser.selfCertification.preferredHashAlgorithms; hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? pref_algo : hash_algo; } @@ -1426,19 +1399,19 @@ export function getPreferredHashAlgo(key) { * @param {Array} keys Set of keys * @returns {enums.symmetric} Preferred symmetric algorithm */ -export function getPreferredSymAlgo(keys) { +export async function getPreferredSymAlgo(keys) { const prioMap = {}; - keys.forEach(function(key) { - const primaryUser = key.getPrimaryUser(); - if (!primaryUser || !primaryUser.selfCertificate.preferredSymmetricAlgorithms) { + await Promise.all(keys.map(async function(key) { + const primaryUser = await key.getPrimaryUser(); + if (!primaryUser || !primaryUser.selfCertification.preferredSymmetricAlgorithms) { return config.encryption_cipher; } - primaryUser.selfCertificate.preferredSymmetricAlgorithms.forEach(function(algo, index) { + primaryUser.selfCertification.preferredSymmetricAlgorithms.forEach(function(algo, index) { const entry = prioMap[algo] || (prioMap[algo] = { prio: 0, count: 0, algo: algo }); entry.prio += 64 >> index; entry.count++; }); - }); + })); let prefAlgo = { prio: 0, algo: config.encryption_cipher }; for (const algo in prioMap) { try { diff --git a/src/message.js b/src/message.js index dcfe0836..0ce048b9 100644 --- a/src/message.js +++ b/src/message.js @@ -253,7 +253,7 @@ Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard symAlgo = sessionKey.algorithm; sessionKey = sessionKey.data; } else if (keys && keys.length) { - symAlgo = enums.read(enums.symmetric, getPreferredSymAlgo(keys)); + symAlgo = enums.read(enums.symmetric, await getPreferredSymAlgo(keys)); } else if (passwords && passwords.length) { symAlgo = enums.read(enums.symmetric, config.encryption_cipher); } else { @@ -303,8 +303,7 @@ export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwor if (publicKeys) { const results = await Promise.all(publicKeys.map(async function(publicKey) { - await publicKey.verifyKeyPackets(undefined, date); - const encryptionKeyPacket = publicKey.getEncryptionKeyPacket(undefined, date); + const encryptionKeyPacket = await publicKey.getEncryptionKeyPacket(undefined, date); if (!encryptionKeyPacket) { throw new Error('Could not find valid key packet for encryption in key ' + publicKey.primaryKey.getKeyId().toHex()); @@ -397,15 +396,14 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } - await privateKey.verifyKeyPackets(undefined, date); - const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); + const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); } const onePassSig = new packet.OnePassSignature(); onePassSig.type = signatureType; - onePassSig.hashAlgorithm = getPreferredHashAlgo(privateKey); + onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey); onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm; onePassSig.signingKeyId = signingKeyPacket.getKeyId(); if (i === privateKeys.length - 1) { @@ -476,8 +474,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } - await privateKey.verifyKeyPackets(undefined, date); - const signingKeyPacket = privateKey.getSigningKeyPacket(undefined, date); + const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date); if (!signingKeyPacket) { throw new Error('Could not find valid key packet for signing in key ' + privateKey.primaryKey.getKeyId().toHex()); @@ -488,7 +485,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig const signaturePacket = new packet.Signature(date); signaturePacket.signatureType = signatureType; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; - signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); + signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey); await signaturePacket.sign(signingKeyPacket, literalDataPacket); return signaturePacket; })).then(signatureList => { @@ -550,8 +547,7 @@ export async function createVerificationObjects(signatureList, literalDataList, let keyPacket = null; await Promise.all(keys.map(async function(key) { // Look for the unique key packet that matches issuerKeyId of signature - await key.verifyKeyPackets(signature.issuerKeyId, date); - const result = key.getSigningKeyPacket(signature.issuerKeyId, date); + const result = await key.getSigningKeyPacket(signature.issuerKeyId, date); if (result) { keyPacket = result; } diff --git a/src/packet/signature.js b/src/packet/signature.js index d2bf41ef..8f405adf 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -667,9 +667,9 @@ Signature.prototype.verify = async function (key, data) { * @return {Boolean} true if expired */ Signature.prototype.isExpired = function (date=new Date()) { - if (date !== null) { + const normDate = util.normalizeDate(date); + if (normDate !== null) { const expirationTime = !this.signatureNeverExpires ? this.created.getTime() + this.signatureExpirationTime*1000 : Infinity; - const normDate = util.normalizeDate(date); return !(this.created <= normDate && normDate < expirationTime); } return false; diff --git a/test/general/key.js b/test/general/key.js index 56be6152..c9d36904 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -761,7 +761,7 @@ describe('Key', function() { )).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); }); - it('Evaluate key flags to find valid encryption key packet', function() { + it('Evaluate key flags to find valid encryption key packet', async function() { const pubKeys = openpgp.key.readArmored(pub_sig_test); expect(pubKeys).to.exist; expect(pubKeys.err).to.not.exist; @@ -771,24 +771,25 @@ describe('Key', function() { // remove subkeys pubKey.subKeys = []; // primary key has only key flags for signing - const keyPacket = pubKey.getEncryptionKeyPacket(); + await pubKey.verifyKeyPackets(); + const keyPacket = await pubKey.getEncryptionKeyPacket(); expect(keyPacket).to.not.exist; }); - it('Method getExpirationTime V4 Key', function() { + it('Method getExpirationTime V4 Key', async function() { const pubKey = openpgp.key.readArmored(twoKeys).keys[1]; expect(pubKey).to.exist; expect(pubKey).to.be.an.instanceof(openpgp.key.Key); - return pubKey.verifyPrimaryUser().then(() => { - expect(pubKey.getExpirationTime().toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); - }); + const expirationTime = await pubKey.getExpirationTime(); + expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); }); - it('Method getExpirationTime V4 SubKey', function() { + it('Method getExpirationTime V4 SubKey', async function() { const pubKey = openpgp.key.readArmored(twoKeys).keys[1]; expect(pubKey).to.exist; expect(pubKey).to.be.an.instanceof(openpgp.key.Key); - expect(pubKey.subKeys[0].getExpirationTime().toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); + const expirationTime = await pubKey.subKeys[0].getExpirationTime(); + expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); }); it('update() - throw error if fingerprints not equal', function(done) { @@ -926,34 +927,30 @@ describe('Key', function() { }); }); - it('getPreferredSymAlgo() - one key - AES256', function() { + it('getPreferredSymAlgo() - one key - AES256', async function() { const key1 = openpgp.key.readArmored(twoKeys).keys[0]; - return key1.verifyPrimaryUser().then(() => { - const prefAlgo = openpgp.key.getPreferredSymAlgo([key1]); - expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes256); - }); + const prefAlgo = await openpgp.key.getPreferredSymAlgo([key1]); + expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes256); }); - it('getPreferredSymAlgo() - two key - AES128', function() { + it('getPreferredSymAlgo() - two key - AES128', async function() { const keys = openpgp.key.readArmored(twoKeys).keys; const key1 = keys[0]; const key2 = keys[1]; - return Promise.all([key1.verifyPrimaryUser(), key2.verifyPrimaryUser()]).then(() => { - key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = [6,7,3]; - const prefAlgo = openpgp.key.getPreferredSymAlgo([key1, key2]); - expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes128); - }); + const primaryUser = await key2.getPrimaryUser(); + primaryUser.selfCertification.preferredSymmetricAlgorithms = [6,7,3]; + const prefAlgo = await openpgp.key.getPreferredSymAlgo([key1, key2]); + expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes128); }); - it('getPreferredSymAlgo() - two key - one without pref', function() { + it('getPreferredSymAlgo() - two key - one without pref', async function() { const keys = openpgp.key.readArmored(twoKeys).keys; const key1 = keys[0]; const key2 = keys[1]; - return Promise.all([key1.verifyPrimaryUser(), key2.verifyPrimaryUser()]).then(() => { - key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = null; - const prefAlgo = openpgp.key.getPreferredSymAlgo([key1, key2]); - expect(prefAlgo).to.equal(openpgp.config.encryption_cipher); - }); + const primaryUser = await key2.getPrimaryUser(); + primaryUser.selfCertification.preferredSymmetricAlgorithms = null; + const prefAlgo = await openpgp.key.getPreferredSymAlgo([key1, key2]); + expect(prefAlgo).to.equal(openpgp.config.encryption_cipher); }); it('Preferences of generated key', function() { @@ -986,14 +983,12 @@ describe('Key', function() { expect(key.users[1].userAttribute).eql(key2.users[1].userAttribute); }); - it('getPrimaryUser()', function() { + it('getPrimaryUser()', async function() { const key = openpgp.key.readArmored(pub_sig_test).keys[0]; - return key.verifyPrimaryUser().then(() => { - const primUser = key.getPrimaryUser(); - expect(primUser).to.exist; - expect(primUser.user.userId.userid).to.equal('Signature Test '); - expect(primUser.selfCertificate).to.be.an.instanceof(openpgp.packet.Signature); - }); + const primUser = await key.getPrimaryUser(); + expect(primUser).to.exist; + expect(primUser.user.userId.userid).to.equal('Signature Test '); + expect(primUser.selfCertification).to.be.an.instanceof(openpgp.packet.Signature); }); it('Generated key is not unlocked by default', function() { @@ -1001,10 +996,10 @@ describe('Key', function() { if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys let key; return openpgp.generateKey(opt).then(function(newKey) { - key = newKey; - return openpgp.message.fromText('hello').encrypt([key.key]); + key = newKey.key; + return openpgp.message.fromText('hello').encrypt([key]); }).then(function(msg) { - return msg.message.decrypt([key.key]); + return msg.message.decrypt([key]); }).catch(function(err) { expect(err.message).to.equal('Private key is not decrypted.'); }); @@ -1061,113 +1056,99 @@ describe('Key', function() { const userId = 'test '; const opt = {numBits: 512, userIds: userId, passphrase: '123', keyExpirationTime: expect_delta}; if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys - return openpgp.generateKey(opt).then(function(key) { + return openpgp.generateKey(opt).then(async function(key) { key = key.key; - return key.verifyPrimaryUser().then(() => { - const expiration = key.getExpirationTime(); - expect(expiration).to.exist; + const expiration = await key.getExpirationTime(); + expect(expiration).to.exist; - const actual_delta = (new Date(expiration) - new Date()) / 1000; - expect(Math.abs(actual_delta - expect_delta)).to.be.below(60); + const actual_delta = (new Date(expiration) - new Date()) / 1000; + expect(Math.abs(actual_delta - expect_delta)).to.be.below(60); - const subKeyExpiration = key.subKeys[0].getExpirationTime(); - expect(subKeyExpiration).to.exist; + const subKeyExpiration = await key.subKeys[0].getExpirationTime(); + expect(subKeyExpiration).to.exist; - const actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000; - expect(Math.abs(actual_subKeyDelta - expect_delta)).to.be.below(60); - }); + const actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000; + expect(Math.abs(actual_subKeyDelta - expect_delta)).to.be.below(60); }); }); - it('Sign and verify key - primary user', function() { - const key = openpgp.key.readArmored(pub_sig_test).keys[0]; + it('Sign and verify key - primary user', async function() { + let publicKey = openpgp.key.readArmored(pub_sig_test).keys[0]; const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; - privateKey.decrypt('hello world'); - return key.signPrimaryUser([privateKey]).then(key => { - return Promise.all( - [key.verifyPrimaryUser([privateKey]), privateKey.verifyPrimaryUser()] - ).then(results => { - const signatures = results[0]; - expect(signatures.length).to.equal(2); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - }); - }); + await privateKey.decrypt('hello world'); + publicKey = await publicKey.signPrimaryUser([privateKey]); + const signatures = await publicKey.verifyPrimaryUser([privateKey]); + const publicKeyPacket = await publicKey.getSigningKeyPacket(); + const privateKeyPacket = await privateKey.getSigningKeyPacket(); + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(publicKeyPacket.getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKeyPacket.getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; }); - it('Sign key and verify with wrong key - primary user', function() { - const key = openpgp.key.readArmored(pub_sig_test).keys[0]; + it('Sign key and verify with wrong key - primary user', async function() { + let publicKey = openpgp.key.readArmored(pub_sig_test).keys[0]; const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; const wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; - privateKey.decrypt('hello world'); - return key.signPrimaryUser([privateKey]).then(key => { - return Promise.all( - [key.verifyPrimaryUser([wrongKey]), privateKey.verifyPrimaryUser()] - ).then(results => { - const signatures = results[0]; - expect(signatures.length).to.equal(2); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.null; - }); - }); + await privateKey.decrypt('hello world'); + publicKey = await publicKey.signPrimaryUser([privateKey]); + const signatures = await publicKey.verifyPrimaryUser([wrongKey]); + const publicKeyPacket = await publicKey.getSigningKeyPacket(); + const privateKeyPacket = await privateKey.getSigningKeyPacket(); + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(publicKeyPacket.getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKeyPacket.getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; }); - it('Sign and verify key - all users', function() { - const key = openpgp.key.readArmored(multi_uid_key).keys[0]; + it('Sign and verify key - all users', async function() { + let publicKey = openpgp.key.readArmored(multi_uid_key).keys[0]; const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; - privateKey.decrypt('hello world'); - return key.signAllUsers([privateKey]).then(key => { - return Promise.all( - [key.verifyAllUsers([privateKey]), key.verifyPrimaryUser(), privateKey.verifyPrimaryUser()] - ).then(results => { - const signatures = results[0]; - expect(signatures.length).to.equal(4); - expect(signatures[0].userid).to.equal(key.users[0].userId.userid); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].userid).to.equal(key.users[0].userId.userid); - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - expect(signatures[2].userid).to.equal(key.users[1].userId.userid); - expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[2].valid).to.be.null; - expect(signatures[3].userid).to.equal(key.users[1].userId.userid); - expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[3].valid).to.be.true; - }); - }); + await privateKey.decrypt('hello world'); + publicKey = await publicKey.signAllUsers([privateKey]); + const signatures = await publicKey.verifyAllUsers([privateKey]); + const publicKeyPacket = await publicKey.getSigningKeyPacket(); + const privateKeyPacket = await privateKey.getSigningKeyPacket(); + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(publicKey.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(publicKeyPacket.getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(publicKey.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKeyPacket.getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + expect(signatures[2].userid).to.equal(publicKey.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(publicKeyPacket.getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(publicKey.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKeyPacket.getKeyId().toHex()); + expect(signatures[3].valid).to.be.true; }); - it('Sign key and verify with wrong key - all users', function() { - const key = openpgp.key.readArmored(multi_uid_key).keys[0]; + it('Sign key and verify with wrong key - all users', async function() { + let publicKey = openpgp.key.readArmored(multi_uid_key).keys[0]; const privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; const wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; - privateKey.decrypt('hello world'); - return key.signAllUsers([privateKey]).then(key => { - return Promise.all( - [key.verifyAllUsers([wrongKey]), key.verifyPrimaryUser(), privateKey.verifyPrimaryUser()] - ).then(results => { - const signatures = results[0]; - expect(signatures.length).to.equal(4); - expect(signatures[0].userid).to.equal(key.users[0].userId.userid); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].userid).to.equal(key.users[0].userId.userid); - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.null; - expect(signatures[2].userid).to.equal(key.users[1].userId.userid); - expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[2].valid).to.be.null; - expect(signatures[3].userid).to.equal(key.users[1].userId.userid); - expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[3].valid).to.be.null; - }); - }); + await privateKey.decrypt('hello world'); + publicKey = await publicKey.signAllUsers([privateKey]); + const signatures = await publicKey.verifyAllUsers([wrongKey]); + const publicKeyPacket = await publicKey.getSigningKeyPacket(); + const privateKeyPacket = await privateKey.getSigningKeyPacket() + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(publicKey.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(publicKeyPacket.getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(publicKey.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKeyPacket.getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; + expect(signatures[2].userid).to.equal(publicKey.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(publicKeyPacket.getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(publicKey.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKeyPacket.getKeyId().toHex()); + expect(signatures[3].valid).to.be.null; }); it('Reformat key without passphrase', function() { @@ -1213,9 +1194,12 @@ describe('Key', function() { expect(newKey.users[0].userId.userid).to.equal(userId); expect(newKey.primaryKey.isDecrypted).to.be.true; return openpgp.sign({data: 'hello', privateKeys: newKey, armor: true}).then(function(signed) { - return openpgp.verify({message: openpgp.cleartext.readArmored(signed.data), publicKeys: newKey.toPublic()}).then(function(verified) { + return openpgp.verify( + {message: openpgp.cleartext.readArmored(signed.data), publicKeys: newKey.toPublic()} + ).then(async function(verified) { expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(newKey.getSigningKeyPacket().getKeyId().toHex()); + const newKeyPacket = await newKey.getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(newKeyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1277,11 +1261,10 @@ describe('Key', function() { }); }); - it('Find a valid subkey binding signature among many invalid ones', function() { - const k = openpgp.key.readArmored(valid_binding_sig_among_many_expired_sigs_pub).keys[0]; - return k.verifyKeyPackets().then(() => { - expect(k.getEncryptionKeyPacket()).to.not.be.null; - }) + it('Find a valid subkey binding signature among many invalid ones', async function() { + const key = openpgp.key.readArmored(valid_binding_sig_among_many_expired_sigs_pub).keys[0]; + await key.verifyKeyPackets(); + expect(await key.getEncryptionKeyPacket()).to.not.be.null; }); it('Reject encryption with revoked subkey', function() { diff --git a/test/general/openpgp.js b/test/general/openpgp.js index e678d292..c96746c0 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -626,7 +626,7 @@ describe('OpenPGP.js public api tests', function() { zero_copyVal = openpgp.config.zero_copy; use_nativeVal = openpgp.config.use_native; aead_protectVal = openpgp.config.aead_protect; - privateKey.keys[0].verifyPrimaryUser().then(() => done()); + done(); }); afterEach(function() { @@ -729,7 +729,6 @@ describe('OpenPGP.js public api tests', function() { beforeEach(async function() { expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; - await privateKey.keys[0].verifyPrimaryUser(); return true; }); @@ -868,12 +867,8 @@ describe('OpenPGP.js public api tests', function() { '=6XMW\r\n' + '-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n'; - beforeEach( async function () { + beforeEach(async function () { expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; - await privateKey.keys[0].verifyPrimaryUser(); - await privateKey_2000_2008.keys[0].verifyPrimaryUser(); - await privateKey_1337.keys[0].verifyPrimaryUser(); - await privateKey_2038_2045.keys[0].verifyPrimaryUser(); return true; }); @@ -1039,10 +1034,11 @@ describe('OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1060,10 +1056,11 @@ describe('OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(''); expect(decrypted.signatures[0].valid).to.be.true; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1083,10 +1080,11 @@ describe('OpenPGP.js public api tests', function() { decOpt.message = openpgp.message.readArmored(encrypted.data); decOpt.signature = openpgp.signature.readArmored(encrypted.signature); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1116,10 +1114,11 @@ describe('OpenPGP.js public api tests', function() { decOpt.message = openpgp.message.readArmored(encrypted.data); decOpt.signature = openpgp.signature.readArmored(encrypted.signature); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1153,16 +1152,17 @@ describe('OpenPGP.js public api tests', function() { }).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { + let keyPacket; expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[1].valid).to.be.true; - return privKeyDE.verifyPrimaryUser().then(() => { - expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); - expect(decrypted.signatures[1].signature.packets.length).to.equal(1); - }); + keyPacket = await privKeyDE.getSigningKeyPacket(); + expect(decrypted.signatures[1].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); + expect(decrypted.signatures[1].signature.packets.length).to.equal(1); }); }); @@ -1191,10 +1191,11 @@ describe('OpenPGP.js public api tests', function() { decOpt.message = openpgp.message.readArmored(encrypted.data); decOpt.signature = openpgp.signature.readArmored(encrypted.signature); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1222,10 +1223,11 @@ describe('OpenPGP.js public api tests', function() { }).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1243,10 +1245,11 @@ describe('OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1264,10 +1267,11 @@ describe('OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(''); expect(decrypted.signatures[0].valid).to.be.null; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1284,10 +1288,11 @@ describe('OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1307,10 +1312,11 @@ describe('OpenPGP.js public api tests', function() { decOpt.message = openpgp.message.readArmored(encrypted.data); decOpt.signature = openpgp.signature.readArmored(encrypted.signature); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1335,16 +1341,17 @@ describe('OpenPGP.js public api tests', function() { return openpgp.encrypt(encOpt).then(function (encrypted) { decOpt.message = openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); - }).then(function (decrypted) { + }).then(async function (decrypted) { + let keyPacket; expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[1].valid).to.be.true; - return privKeyDE.verifyPrimaryUser().then(() => { - expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); - expect(decrypted.signatures[1].signature.packets.length).to.equal(1); - }); + keyPacket = await privKeyDE.getSigningKeyPacket(); + expect(decrypted.signatures[1].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); + expect(decrypted.signatures[1].signature.packets.length).to.equal(1); }); }); @@ -1360,10 +1367,11 @@ describe('OpenPGP.js public api tests', function() { expect(signed.data).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); verifyOpt.message = openpgp.cleartext.readArmored(signed.data); return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(async function (verified) { expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1383,16 +1391,17 @@ describe('OpenPGP.js public api tests', function() { expect(signed.data).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); verifyOpt.message = openpgp.cleartext.readArmored(signed.data); return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(async function (verified) { + let keyPacket; expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); expect(verified.signatures[1].valid).to.be.true; - return privKeyDE.verifyPrimaryUser().then(() => { - expect(verified.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); - expect(verified.signatures[1].signature.packets.length).to.equal(1); - }); + keyPacket = await privKeyDE.getSigningKeyPacket(); + expect(verified.signatures[1].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); + expect(verified.signatures[1].signature.packets.length).to.equal(1); }); }); @@ -1409,10 +1418,11 @@ describe('OpenPGP.js public api tests', function() { verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); verifyOpt.signature = openpgp.signature.readArmored(signed.signature); return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(async function (verified) { expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1428,10 +1438,11 @@ describe('OpenPGP.js public api tests', function() { return openpgp.sign(signOpt).then(function (signed) { verifyOpt.message = openpgp.cleartext.readArmored(signed.data); return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(async function (verified) { expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.null; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1449,10 +1460,11 @@ describe('OpenPGP.js public api tests', function() { verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); verifyOpt.signature = openpgp.signature.readArmored(signed.signature); return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(async function (verified) { expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.null; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1469,10 +1481,11 @@ describe('OpenPGP.js public api tests', function() { return openpgp.sign(signOpt).then(function (signed) { verifyOpt.message = signed.message; return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(async function (verified) { expect(verified.data).to.equal(plaintext); expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1492,12 +1505,13 @@ describe('OpenPGP.js public api tests', function() { verifyOpt.message = new openpgp.cleartext.CleartextMessage(plaintext); verifyOpt.signature = signed.signature; return openpgp.verify(verifyOpt); - }).then(function (verified) { + }).then(async function (verified) { expect(verified.data).to.equal(plaintext); expect(+verified.signatures[0].signature.packets[0].created).to.be.lte(+openpgp.util.normalizeDate()); expect(+verified.signatures[0].signature.packets[0].created).to.be.gte(+start); expect(verified.signatures[0].valid).to.be.true; - expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + const keyPacket = await privateKey.keys[0].getSigningKeyPacket(); + expect(verified.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1688,14 +1702,13 @@ describe('OpenPGP.js public api tests', function() { publicKeys: pubKeyDE, message: openpgp.message.readArmored(encrypted.data) }); - }).then(function (decrypted) { + }).then(async function (decrypted) { expect(decrypted.data).to.exist; expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - return privKeyDE.verifyPrimaryUser().then(() => { - expect(decrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); - expect(decrypted.signatures[0].signature.packets.length).to.equal(1); - }); + const keyPacket = await privKeyDE.getSigningKeyPacket(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(keyPacket.getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); }); From 6fefe22c098d9cee48402dbab836ad83157be403 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Wed, 7 Mar 2018 15:56:43 -0800 Subject: [PATCH 6/6] Finished fixing key.js; fixes async tests --- src/key.js | 82 +++++++++++++++++---------------------- test/crypto/elliptic.js | 8 ++-- test/general/key.js | 18 ++++----- test/general/openpgp.js | 32 +++++++-------- test/general/packet.js | 10 ++--- test/general/signature.js | 32 +++++++-------- test/general/x25519.js | 34 ++++++++-------- 7 files changed, 101 insertions(+), 115 deletions(-) diff --git a/src/key.js b/src/key.js index 71e23c71..114915fa 100644 --- a/src/key.js +++ b/src/key.js @@ -431,7 +431,7 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { return enums.keyStatus.no_self_cert; } // check for valid, unrevoked, unexpired self signature - const { user, selfCertification } = await this.getPrimaryUser(date); + const { user, selfCertification } = await this.getPrimaryUser(date) || {}; if (!user) { return enums.keyStatus.invalid; } @@ -469,21 +469,41 @@ Key.prototype.getExpirationTime = async function() { * @returns {{user: Array, selfCertification: Array}|null} The primary user and the self signature */ Key.prototype.getPrimaryUser = async function(date=new Date()) { - // FIXME - await this.verifyPrimaryUser(); + const { primaryKey } = this; let primaryUsers = []; - this.users.forEach((user, i) => { + let lastCreated = null; + let lastPrimaryUserID = null; + // TODO replace when Promise.forEach is implemented + for (let i = 0; i < this.users.length; i++) { + const user = this.users[i]; if (!user.userId) { return; } - user.selfCertifications.forEach(cert => { - // only consider already validated certificates - if (!cert.verified || cert.revoked || cert.isExpired(date)) { - return; + const dataToVerify = { userid: user.userId , key: primaryKey }; + for (let j = 0; j < user.selfCertifications.length; j++) { + const cert = user.selfCertifications[j]; + // skip if certificate is not the most recent + if ((cert.isPrimaryUserID && cert.isPrimaryUserID < lastPrimaryUserID) || + (!lastPrimaryUserID && cert.created < lastCreated)) { + continue; } + // skip if certificates is invalid, revoked, or expired + // eslint-disable-next-line no-await-in-loop + if (!(cert.verified || await cert.verify(primaryKey, dataToVerify))) { + continue; + } + // eslint-disable-next-line no-await-in-loop + if (cert.revoked || await user.isRevoked(primaryKey, cert, null, date)) { + continue; + } + if (cert.isExpired(date)) { + continue; + } + lastPrimaryUserID = cert.isPrimaryUserID; + lastCreated = cert.created; primaryUsers.push({ index: i, user: user, selfCertification: cert }); - }); - }); + } + } // sort by primary user flag and signature creation time primaryUsers = primaryUsers.sort(function(a, b) { const A = a.selfCertification; @@ -630,42 +650,12 @@ Key.prototype.signAllUsers = async function(privateKeys) { * valid: Boolean}>>} List of signer's keyid and validity of signature */ Key.prototype.verifyPrimaryUser = async function(keys) { - const { primaryKey } = this; - const primaryUsers = []; - let lastCreated = null; - let lastPrimaryUserID = null; - await Promise.all(this.users.map(async function(user) { - if (!user.userId && !user.userAttribute) { - return; - } - const dataToVerify = { userid: user.userId || user.userAttribute, key: primaryKey }; - // TODO replace when Promise.forEach is implemented - for (let i = 0; i < user.selfCertifications.length; i++) { - const cert = user.selfCertifications[i]; - // skip if certificate is not the most recent - if ((cert.isPrimaryUserID && cert.isPrimaryUserID < lastPrimaryUserID) || - (!lastPrimaryUserID && cert.created < lastCreated)) { - return; - } - // skip if certificates is invalid, revoked, or expired - // eslint-disable-next-line no-await-in-loop - if (!(cert.verified || await cert.verify(primaryKey, dataToVerify))) { - return; - } - // eslint-disable-next-line no-await-in-loop - if (cert.revoked || await user.isRevoked(primaryKey, cert)) { - return; - } - if (cert.isExpired()) { - return; - } - lastPrimaryUserID = cert.isPrimaryUserID; - lastCreated = cert.created; - primaryUsers.push(user); - } - })); - const user = primaryUsers.pop(); - const results = !user ? [] : keys ? await user.verifyAllCertifications(primaryKey, keys) : + const primaryKey = this.primaryKey; + const { user } = await this.getPrimaryUser() || {}; + if (!user) { + throw new Error('Could not find primary user'); + } + const results = keys ? await user.verifyAllCertifications(primaryKey, keys) : [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey) === enums.keyStatus.valid }]; return results; }; diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index afee0f55..26f816b8 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -192,9 +192,9 @@ describe('Elliptic Curve Cryptography', function () { it('Signature generation', function () { const curve = new elliptic_curves.Curve('p256'); let key = curve.keyFromPrivate(key_data.p256.priv); - return key.sign(signature_data.message, 8).then(signature => { + return key.sign(signature_data.message, 8).then(async signature => { key = curve.keyFromPublic(key_data.p256.pub); - expect( + await expect( key.verify(signature_data.message, signature, 8) ).to.eventually.be.true; }); @@ -302,8 +302,8 @@ describe('Elliptic Curve Cryptography', function () { const keyPrivate = new Uint8Array(keyPair.getPrivate()); const oid = curve.oid; const message = p384_message; - return elliptic_curves.ecdsa.sign(oid, 10, message, keyPrivate).then(signature => { - expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic)) + return elliptic_curves.ecdsa.sign(oid, 10, message, keyPrivate).then(async signature => { + await expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic)) .to.eventually.be.true; }); }); diff --git a/test/general/key.js b/test/general/key.js index c9d36904..4e09926d 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -810,15 +810,14 @@ describe('Key', function() { }); }); - it('update() - merge user', function(done) { + it('update() - merge user', function() { const source = openpgp.key.readArmored(pub_sig_test).keys[0]; const dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.users[1]).to.exist; dest.users.pop(); - dest.update(source).then(() => { + return dest.update(source).then(() => { expect(dest.users[1]).to.exist; expect(dest.users[1].userId).to.equal(source.users[1].userId); - done(); }); }); @@ -912,18 +911,17 @@ describe('Key', function() { .to.be.rejectedWith('Cannot update public key with private key if subkey mismatch').notify(done); }); - it('update() - merge subkey binding signatures', function(done) { + it('update() - merge subkey binding signatures', async function() { const source = openpgp.key.readArmored(pgp_desktop_pub).keys[0]; const dest = openpgp.key.readArmored(pgp_desktop_priv).keys[0]; expect(source.subKeys[0].bindingSignatures[0]).to.exist; - expect(source.subKeys[0].verify(source.primaryKey)) + await expect(source.subKeys[0].verify(source.primaryKey)) .to.eventually.equal(openpgp.enums.keyStatus.valid); expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist; - dest.update(source).then(() => { + return dest.update(source).then(async () => { expect(dest.subKeys[0].bindingSignatures[0]).to.exist; - expect(dest.subKeys[0].verify(source.primaryKey)) + await expect(dest.subKeys[0].verify(source.primaryKey)) .to.eventually.equal(openpgp.enums.keyStatus.valid); - done(); }); }); @@ -1217,12 +1215,12 @@ describe('Key', function() { opt.privateKey = key; opt.userIds = [userId2, userId3]; opt.passphrase = '123'; - return openpgp.reformatKey(opt).then(function(newKey) { + return openpgp.reformatKey(opt).then(async function(newKey) { newKey = newKey.key; expect(newKey.users.length).to.equal(2); expect(newKey.users[0].userId.userid).to.equal(userId2); expect(newKey.primaryKey.isDecrypted).to.be.false; - newKey.decrypt('123'); + await newKey.decrypt('123'); expect(newKey.primaryKey.isDecrypted).to.be.true; }); }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index c96746c0..72f62492 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -635,8 +635,8 @@ describe('OpenPGP.js public api tests', function() { openpgp.config.aead_protect = aead_protectVal; }); - it('Decrypting key with wrong passphrase rejected', function () { - expect(privateKey.keys[0].decrypt('wrong passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase'); + it('Decrypting key with wrong passphrase rejected', async function () { + await expect(privateKey.keys[0].decrypt('wrong passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase'); }); it('Decrypting key with correct passphrase returns true', async function () { @@ -891,9 +891,9 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt then decrypt with multiple private keys', function () { + it('should encrypt then decrypt with multiple private keys', async function () { const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; - privKeyDE.decrypt(passphrase); + await privKeyDE.decrypt(passphrase); const encOpt = { data: plaintext, @@ -933,9 +933,9 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt then decrypt with wildcard with multiple private keys', function () { + it('should encrypt then decrypt with wildcard with multiple private keys', async function () { const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; - privKeyDE.decrypt(passphrase); + await privKeyDE.decrypt(passphrase); const encOpt = { data: plaintext, @@ -1123,9 +1123,9 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption', function () { + it('should encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption', async function () { const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; - privKeyDE.decrypt(passphrase); + await privKeyDE.decrypt(passphrase); const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; @@ -1321,9 +1321,9 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt and decrypt/verify both signatures when signed with two private keys', function () { + it('should encrypt and decrypt/verify both signatures when signed with two private keys', async function () { const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; - privKeyDE.decrypt(passphrase); + await privKeyDE.decrypt(passphrase); const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; @@ -1376,9 +1376,9 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('should sign and verify cleartext data with multiple private keys', function () { + it('should sign and verify cleartext data with multiple private keys', async function () { const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; - privKeyDE.decrypt(passphrase); + await privKeyDE.decrypt(passphrase); const signOpt = { data: plaintext, @@ -1688,10 +1688,10 @@ describe('OpenPGP.js public api tests', function() { describe('ELG / DSA encrypt, decrypt, sign, verify', function() { - it('round trip test', function () { + it('round trip test', async function () { const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; - privKeyDE.decrypt(passphrase); + await privKeyDE.decrypt(passphrase); return openpgp.encrypt({ publicKeys: pubKeyDE, privateKeys: privKeyDE, @@ -1765,9 +1765,9 @@ describe('OpenPGP.js public api tests', function() { '=IkKW', '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); - it('Decrypt message', function() { + it('Decrypt message', async function() { const privKey = openpgp.key.readArmored(priv_key).keys[0]; - privKey.decrypt('1234'); + await privKey.decrypt('1234'); const message = openpgp.message.readArmored(pgp_msg); return openpgp.decrypt({ privateKeys:privKey, message:message }).then(function(decrypted) { diff --git a/test/general/packet.js b/test/general/packet.js index 1c07bd76..ec9915e0 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -95,7 +95,7 @@ describe("Packet", function() { const msg2 = new openpgp.packet.List(); msg2.read(message.write()); - expect(msg2[0].decrypt(algo, key)).to.eventually.be.rejectedWith('Decryption failed due to missing MDC in combination with modern cipher.'); + await expect(msg2[0].decrypt(algo, key)).to.eventually.be.rejectedWith('Decryption failed due to missing MDC in combination with modern cipher.'); }); it('Sym. encrypted integrity protected packet', async function() { @@ -415,7 +415,7 @@ describe("Packet", function() { const payload = msg[1].packets[0].packets; - expect(payload[2].verify( + await expect(payload[2].verify( key[0], payload[1] )).to.eventually.be.true; }); @@ -473,7 +473,7 @@ describe("Packet", function() { signature.publicKeyAlgorithm = 'rsa_sign'; signature.signatureType = 'binary'; - signature.sign(key, literal).then(() => { + signature.sign(key, literal).then(async () => { signed.push(literal); signed.push(signature); @@ -483,8 +483,8 @@ describe("Packet", function() { const signed2 = new openpgp.packet.List(); signed2.read(raw); - expect(signed2[1].verify(key, signed2[0])).to.eventually.be.true; - }); + await expect(signed2[1].verify(key, signed2[0])).to.eventually.be.true; + }); }); }); }); diff --git a/test/general/signature.js b/test/general/signature.js index 0728e126..5db953e0 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -324,11 +324,11 @@ describe("Signature", function() { '=Q4tk', '-----END PGP MESSAGE-----'].join('\n'); - it('Testing signature checking on CAST5-enciphered message', function() { + it('Testing signature checking on CAST5-enciphered message', async function() { const priv_key = openpgp.key.readArmored(priv_key_arm1).keys[0]; const pub_key = openpgp.key.readArmored(pub_key_arm1).keys[0]; const msg = openpgp.message.readArmored(msg_arm1); - priv_key.decrypt("abcd"); + await priv_key.decrypt("abcd"); return openpgp.decrypt({ privateKeys: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) { expect(decrypted.data).to.exist; expect(decrypted.signatures[0].valid).to.be.true; @@ -336,7 +336,7 @@ describe("Signature", function() { }); }); - it('Testing GnuPG stripped-key extensions', function() { + it('Testing GnuPG stripped-key extensions', async function() { // exercises the GnuPG s2k type 1001 extension: // the secrets on the primary key have been stripped. const priv_key_gnupg_ext = openpgp.key.readArmored( @@ -369,7 +369,7 @@ describe("Signature", function() { const pub_key = openpgp.key.readArmored(pub_key_arm1).keys[0]; const msg = openpgp.message.readArmored(msg_arm1); - priv_key_gnupg_ext.subKeys[0].subKey.decrypt("abcd"); + await priv_key_gnupg_ext.subKeys[0].subKey.decrypt("abcd"); return msg.decrypt([priv_key_gnupg_ext]).then(function(msg) { return msg.verify([pub_key]).then(verified => { expect(verified).to.exist; @@ -426,7 +426,7 @@ describe("Signature", function() { }); }); - it('Verify signature of signed and encrypted message from GPG2 with openpgp.decrypt', function() { + it('Verify signature of signed and encrypted message from GPG2 with openpgp.decrypt', async function() { const msg_armor = ['-----BEGIN PGP MESSAGE-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -448,7 +448,7 @@ describe("Signature", function() { const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId)); + await Promise.all(esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId))); return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) { expect(decrypted.data).to.exist; @@ -459,7 +459,7 @@ describe("Signature", function() { }); }); - it('Verify signature of signed and encrypted message from PGP 10.3.0 with openpgp.decrypt', function() { + it('Verify signature of signed and encrypted message from PGP 10.3.0 with openpgp.decrypt', async function() { const msg_armor = ['-----BEGIN PGP MESSAGE-----', 'Version: Encryption Desktop 10.3.0 (Build 9307)', @@ -482,7 +482,7 @@ describe("Signature", function() { const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId)); + await Promise.all(esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId))); return openpgp.decrypt({ privateKeys: privKey, publicKeys:[pubKey], message:esMsg }).then(function(decrypted) { expect(decrypted.data).to.exist; @@ -580,11 +580,11 @@ describe("Signature", function() { }); }); - it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures', function() { + it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures', async function() { const plaintext = 'short message\nnext line\n한국어/조선말'; const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.primaryKey.decrypt('hello world'); + await privKey.primaryKey.decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext }).then(function(signed) { @@ -600,11 +600,11 @@ describe("Signature", function() { }); }); - it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - armored', function() { + it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - armored', async function() { const plaintext = openpgp.util.str_to_Uint8Array('short message\nnext line\n한국어/조선말'); const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.primaryKey.decrypt('hello world'); + await privKey.primaryKey.decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext }).then(function(signed) { @@ -620,11 +620,11 @@ describe("Signature", function() { }); }); - it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - not armored', function() { + it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - not armored', async function() { const plaintext = openpgp.util.str_to_Uint8Array('short message\nnext line\n한국어/조선말'); const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.primaryKey.decrypt('hello world'); + await privKey.primaryKey.decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext, armor:false }).then(function(signed) { @@ -786,11 +786,11 @@ describe("Signature", function() { }); }); - it('Detached signature signing and verification', function() { + it('Detached signature signing and verification', async function() { const msg = openpgp.message.fromText('hello'); const pubKey2 = openpgp.key.readArmored(pub_key_arm2).keys[0]; const privKey2 = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey2.decrypt('hello world'); + await privKey2.decrypt('hello world'); const opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null}; if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys diff --git a/test/general/x25519.js b/test/general/x25519.js index 1f1e62ae..c9b5031a 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -217,7 +217,7 @@ describe('X25519 Cryptography', function () { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "ed25519" }; - return openpgp.generateKey(options).then(function (firstKey) { + return openpgp.generateKey(options).then(async function (firstKey) { expect(firstKey).to.exist; expect(firstKey.privateKeyArmored).to.exist; expect(firstKey.publicKeyArmored).to.exist; @@ -236,10 +236,10 @@ describe('X25519 Cryptography', function () { // Self Certificate is valid const user = hi.users[0]; - expect(user.selfCertifications[0].verify( + await expect(user.selfCertifications[0].verify( primaryKey, { userid: user.userId, key: primaryKey } )).to.eventually.be.true; - expect(user.verifyCertificate( + await expect(user.verifyCertificate( primaryKey, user.selfCertifications[0], [hi.toPublic()] )).to.eventually.equal(openpgp.enums.keyStatus.valid); @@ -247,7 +247,7 @@ describe('X25519 Cryptography', function () { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "curve25519" }; - return openpgp.generateKey(options).then(function (secondKey) { + return openpgp.generateKey(options).then(async function (secondKey) { const bye = secondKey.key; expect(bye.primaryKey.params[0].getName()).to.equal('ed25519'); expect(bye.primaryKey.algorithm).to.equal('eddsa'); @@ -256,10 +256,10 @@ describe('X25519 Cryptography', function () { // Self Certificate is valid const user = bye.users[0]; - expect(user.selfCertifications[0].verify( + await expect(user.selfCertifications[0].verify( bye.primaryKey, { userid: user.userId, key: bye.primaryKey } )).to.eventually.be.true; - expect(user.verifyCertificate( + await expect(user.verifyCertificate( bye.primaryKey, user.selfCertifications[0], [bye.toPublic()] )).to.eventually.equal(openpgp.enums.keyStatus.valid); @@ -531,17 +531,15 @@ describe('X25519 Cryptography', function () { '=xeG/', '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const hi = openpgp.key.readArmored(pubKey).keys[0]; - return hi.verifyPrimaryUser().then(() => { - const results = hi.getPrimaryUser(); - expect(results).to.exist; - expect(results.user).to.exist; - const user = results.user; - expect(user.selfCertifications[0].verify( - hi.primaryKey, {userid: user.userId, key: hi.primaryKey} - )).to.eventually.be.true; - expect(user.verifyCertificate( - hi.primaryKey, user.selfCertifications[0], [hi] - )).to.eventually.equal(openpgp.enums.keyStatus.valid); - }); + const results = hi.getPrimaryUser(); + expect(results).to.exist; + expect(results.user).to.exist; + const user = results.user; + expect(user.selfCertifications[0].verify( + hi.primaryKey, {userid: user.userId, key: hi.primaryKey} + )).to.eventually.be.true; + expect(user.verifyCertificate( + hi.primaryKey, user.selfCertifications[0], [hi] + )).to.eventually.equal(openpgp.enums.keyStatus.valid); }); */ });