From 122d526f49015f9b371e58830dcf9a1c38a117db Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 18 Apr 2018 13:45:30 +0200 Subject: [PATCH 1/3] Only consider most recent subkey binding signature This partially reverts 2bda127. --- src/key.js | 82 +++++++++++++----------- test/general/key.js | 152 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+), 38 deletions(-) diff --git a/src/key.js b/src/key.js index 888be85d..6734cf2c 100644 --- a/src/key.js +++ b/src/key.js @@ -248,6 +248,23 @@ Key.prototype.armor = function() { return armor.encode(type, this.toPacketlist().write()); }; +/** + * Returns the signature that has the latest creation date, while ignoring signatures created in the future. + * @param {Array} signatures List of signatures + * @param {Date} date Use the given date instead of the current time + * @returns {module:packet.Signature} The latest signature + */ +function getLatestSignature(signatures, date=new Date()) { + let signature = signatures[0]; + for (let i = 1; i < signatures.length; i++) { + if (signatures[i].created >= signature.created && + signatures[i].created <= date) { + signature = signatures[i]; + } + } + return signature; +} + function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && @@ -278,10 +295,9 @@ Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date()) if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { // eslint-disable-next-line no-await-in-loop if (await this.subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) { - 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; - } + const bindingSignature = getLatestSignature(this.subKeys[i].bindingSignatures, date); + if (isValidSigningKeyPacket(this.subKeys[i].subKey, bindingSignature, date)) { + return this.subKeys[i].subKey; } } } @@ -321,10 +337,9 @@ Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date()) { if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { // eslint-disable-next-line no-await-in-loop if (await this.subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) { - 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; - } + const bindingSignature = getLatestSignature(this.subKeys[i].bindingSignatures, date); + if (isValidEncryptionKeyPacket(this.subKeys[i].subKey, bindingSignature, date)) { + return this.subKeys[i].subKey; } } } @@ -539,6 +554,7 @@ Key.prototype.getValidUsers = async function(date=new Date(), allowExpired=false } return validUsers; }; + /** * Update key with new components from specified key with same key ID: * users, subkeys, certificates are merged into the destination key, @@ -974,42 +990,32 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) { return enums.keyStatus.expired; } // 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 - if (!(bindingSignature.verified || await bindingSignature.verify(primaryKey, dataToVerify))) { - return enums.keyStatus.invalid; - } - // 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 (ie, check for V4 expiration time) - if (bindingSignature.isExpired(date)) { - return enums.keyStatus.expired; - } - 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(); + const bindingSignature = getLatestSignature(this.bindingSignatures, date); + // check binding signature is verified + if (!(bindingSignature.verified || await bindingSignature.verify(primaryKey, dataToVerify))) { + return enums.keyStatus.invalid; + } + // 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 (ie, check for V4 expiration time) + if (bindingSignature.isExpired(date)) { + return enums.keyStatus.expired; + } + return enums.keyStatus.valid; // binding signature passed all checks }; /** * Returns the expiration time of the subkey or Infinity if key does not expire + * @param {Date} date Use the given date instead of the current time * @returns {Date} */ -SubKey.prototype.getExpirationTime = function() { - let highest = null; - for (let i = 0; i < this.bindingSignatures.length; i++) { - const current = Math.min(+getExpirationTime(this.subKey, this.bindingSignatures[i]), +this.bindingSignatures[i].getExpirationTime()); - if (current === Infinity) { - return Infinity; - } - highest = current > highest ? current : highest; - } - return util.normalizeDate(highest); +SubKey.prototype.getExpirationTime = function(date=new Date()) { + const bindingSignature = getLatestSignature(this.bindingSignatures, date); + const keyExpiry = getExpirationTime(this.subKey, bindingSignature); + const sigExpiry = bindingSignature.getExpirationTime(); + return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; }; /** diff --git a/test/general/key.js b/test/general/key.js index e9d09db5..6839f963 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -736,6 +736,153 @@ describe('Key', function() { =/7PI -----END PGP PRIVATE KEY BLOCK-----`; + const multipleBindingSignatures = +`-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2 + +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 +p92yZgB3r2+f6/GIe2+7 +=jZgG +-----END PGP PUBLIC KEY BLOCK-----`; + it('Parsing armored text with two keys', function(done) { const pubKeys = openpgp.key.readArmored(twoKeys); expect(pubKeys).to.exist; @@ -1405,6 +1552,11 @@ describe('Key', function() { expect(await key.getEncryptionKeyPacket()).to.not.be.null; }); + it('Selects the most recent subkey binding signature', async function() { + const key = openpgp.key.readArmored(multipleBindingSignatures).keys[0]; + expect(key.subKeys[0].getExpirationTime().toISOString()).to.equal('2015-10-18T07:41:30.000Z'); + }); + it('Reject encryption with revoked subkey', function() { const key = openpgp.key.readArmored(pub_revoked_subkeys).keys[0]; return openpgp.encrypt({publicKeys: [key], data: 'random data'}).then(() => { From 39c7374d70678dd97d1db2b76a4baada11567128 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 18 Apr 2018 14:15:34 +0200 Subject: [PATCH 2/3] Only consider most recent user self certification --- src/key.js | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/key.js b/src/key.js index 6734cf2c..90d2c759 100644 --- a/src/key.js +++ b/src/key.js @@ -528,29 +528,27 @@ Key.prototype.getValidUsers = async function(date=new Date(), allowExpired=false 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 (!allowExpired && cert.isExpired(date)) { - continue; - } - lastPrimaryUserID = cert.isPrimaryUserID; - lastCreated = cert.created; - validUsers.push({ index: i, user: user, selfCertification: cert }); + const cert = getLatestSignature(user.selfCertifications); + // 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 (!allowExpired && cert.isExpired(date)) { + continue; + } + lastPrimaryUserID = cert.isPrimaryUserID; + lastCreated = cert.created; + validUsers.push({ index: i, user: user, selfCertification: cert }); } return validUsers; }; From ceec57672ed796c255d994c3f383a2871fd7b7f0 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Mon, 23 Apr 2018 13:02:25 +0200 Subject: [PATCH 3/3] Use most recent primary user --- src/key.js | 96 +++++++++++++++++------------------------------------- 1 file changed, 30 insertions(+), 66 deletions(-) diff --git a/src/key.js b/src/key.js index 90d2c759..6587bd72 100644 --- a/src/key.js +++ b/src/key.js @@ -258,7 +258,7 @@ function getLatestSignature(signatures, date=new Date()) { let signature = signatures[0]; for (let i = 1; i < signatures.length; i++) { if (signatures[i].created >= signature.created && - signatures[i].created <= date) { + (signatures[i].created <= date || date === null)) { signature = signatures[i]; } } @@ -453,9 +453,7 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) { return enums.keyStatus.invalid; } // check for expiration time - const currentTime = util.normalizeDate(date); - if ((primaryKey.version === 3 && isDataExpired(primaryKey, null, date)) || - (primaryKey.version === 4 && isDataExpired(primaryKey, selfCertification, date))) { + if (isDataExpired(primaryKey, selfCertification, date)) { return enums.keyStatus.expired; } return enums.keyStatus.valid; @@ -471,17 +469,11 @@ Key.prototype.getExpirationTime = async function() { return getExpirationTime(this.primaryKey); } if (this.primaryKey.version === 4) { - const validUsers = await this.getValidUsers(null, true); - let highest = null; - for (let i = 0; i < validUsers.length; i++) { - const selfCert = validUsers[i].selfCertification; - const current = Math.min(+getExpirationTime(this.primaryKey, selfCert), +selfCert.getExpirationTime()); - if (current === Infinity) { - return Infinity; - } - highest = current > highest ? current : highest; - } - return util.normalizeDate(highest); + const primaryUser = await this.getPrimaryUser(null); + const selfCert = primaryUser.selfCertification; + const keyExpiry = getExpirationTime(this.primaryKey, selfCert); + const sigExpiry = selfCert.getExpirationTime(); + return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; } }; @@ -495,62 +487,34 @@ Key.prototype.getExpirationTime = async function() { * @async */ Key.prototype.getPrimaryUser = async function(date=new Date()) { - let validUsers = await this.getValidUsers(date); - if (!validUsers.length) { - return null; - } // sort by primary user flag and signature creation time - validUsers = validUsers.sort(function(a, b) { + const primaryUser = this.users.map(function(user, index) { + const selfCertification = getLatestSignature(user.selfCertifications, date); + return { index, user, selfCertification }; + }).sort(function(a, b) { const A = a.selfCertification; const B = b.selfCertification; return (A.isPrimaryUserID - B.isPrimaryUserID) || (A.created - B.created); - }); - return validUsers.pop(); -}; - -/** - * Returns an array containing all valid users for a key - * @param {Date} date use the given date for verification instead of the current time - * @param {bool} include users with expired certifications - * @returns {Promise>} The valid user array - * @async - */ -Key.prototype.getValidUsers = async function(date=new Date(), allowExpired=false) { - const { primaryKey } = this; - const validUsers = []; - 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; - } - const dataToVerify = { userid: user.userId , key: primaryKey }; - const cert = getLatestSignature(user.selfCertifications); - // 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 (!allowExpired && cert.isExpired(date)) { - continue; - } - lastPrimaryUserID = cert.isPrimaryUserID; - lastCreated = cert.created; - validUsers.push({ index: i, user: user, selfCertification: cert }); + }).pop(); + const { user, selfCertification: cert } = primaryUser; + if (!user.userId) { + return null; } - return validUsers; + const { primaryKey } = this; + const dataToVerify = { userid: user.userId , key: primaryKey }; + // 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 null; + } + // eslint-disable-next-line no-await-in-loop + if (cert.revoked || await user.isRevoked(primaryKey, cert, null, date)) { + return null; + } + if (cert.isExpired(date)) { + return null; + } + return primaryUser; }; /**