From a49276a158b7d57eba1c1149e857f25d7b6c2605 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 18 Jul 2018 14:04:36 +0200 Subject: [PATCH] Allow checking expiry of subkeys by capability, keyId or userId --- src/key.js | 74 ++++++++++++++++++++++++++++++++++----------- test/general/key.js | 66 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 17 deletions(-) diff --git a/src/key.js b/src/key.js index 3d0641ad..0749a75a 100644 --- a/src/key.js +++ b/src/key.js @@ -276,15 +276,14 @@ function isValidSigningKeyPacket(keyPacket, signature, date=new Date()) { } /** - * Returns last created key packet or key packet by given keyId that is available for signing and verification + * Returns last created key or key by given keyId that is available for signing and verification * @param {module:type/keyid} keyId, optional * @param {Date} date use the given date for verification instead of the current time * @param {Object} userId, optional user ID - * @returns {Promise} key packet or null if no signing key has been found + * @returns {Promise} key or null if no signing key has been found * @async */ -Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date(), userId={}) { +Key.prototype.getSigningKey = async function (keyId=null, date=new Date(), userId={}) { const primaryKey = this.primaryKey; if (await this.verifyPrimaryKey(date, userId) === enums.keyStatus.valid) { const subKeys = this.subKeys.slice().sort((a, b) => b.created - a.created); @@ -294,7 +293,7 @@ Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date(), if (await subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) { const bindingSignature = getLatestSignature(subKeys[i].bindingSignatures, date); if (isValidSigningKeyPacket(subKeys[i].subKey, bindingSignature, date)) { - return subKeys[i].subKey; + return subKeys[i]; } } } @@ -302,12 +301,26 @@ Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date(), const primaryUser = await this.getPrimaryUser(date, userId); if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) && isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification, date)) { - return primaryKey; + return this; } } return null; }; +/** + * Returns last created key packet or key packet by given keyId that is available for signing and verification + * @param {module:type/keyid} keyId, optional + * @param {Date} date use the given date for verification instead of the current time + * @param {Object} userId, optional user ID + * @returns {Promise} key packet or null if no signing key has been found + * @async + */ +Key.prototype.getSigningKeyPacket = async function (keyId=null, date=new Date(), userId={}) { + const signingKey = await this.getSigningKey(keyId, date, userId); + return signingKey && (signingKey.subKey || signingKey.primaryKey); +}; + function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.dsa) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_sign) && @@ -321,17 +334,14 @@ function isValidEncryptionKeyPacket(keyPacket, signature, date=new Date()) { } /** - * Returns last created key packet or key packet by given keyId that is available for encryption or decryption + * Returns last created key or key by given keyId that is available for encryption or decryption * @param {module:type/keyid} keyId, optional * @param {Date} date, optional * @param {String} userId, optional - * @returns {Promise} key packet or null if no encryption key has been found + * @returns {Promise} key or null if no encryption key has been found * @async */ -Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date(), userId={}) { +Key.prototype.getEncryptionKey = async function(keyId, date=new Date(), userId={}) { const primaryKey = this.primaryKey; if (await this.verifyPrimaryKey(date, userId) === enums.keyStatus.valid) { // V4: by convention subkeys are preferred for encryption service @@ -343,7 +353,7 @@ Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date(), us if (await subKeys[i].verify(primaryKey, date) === enums.keyStatus.valid) { const bindingSignature = getLatestSignature(subKeys[i].bindingSignatures, date); if (isValidEncryptionKeyPacket(subKeys[i].subKey, bindingSignature, date)) { - return subKeys[i].subKey; + return subKeys[i]; } } } @@ -352,12 +362,28 @@ Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date(), us const primaryUser = await this.getPrimaryUser(date, userId); if (primaryUser && (!keyId || primaryKey.getKeyId().equals(keyId)) && isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification, date)) { - return primaryKey; + return this; } } return null; }; +/** + * Returns last created key packet or key packet by given keyId that is available for encryption or decryption + * @param {module:type/keyid} keyId, optional + * @param {Date} date, optional + * @param {String} userId, optional + * @returns {Promise} key packet or null if no encryption key has been found + * @async + */ +Key.prototype.getEncryptionKeyPacket = async function(keyId, date=new Date(), userId={}) { + const encryptionKey = await this.getEncryptionKey(keyId, date, userId); + return encryptionKey && (encryptionKey.subKey || encryptionKey.primaryKey); +}; + /** * Encrypts all secret key and subkey packets matching keyId * @param {String|Array} passphrases - if multiple passphrases, then should be in same order as packets each should encrypt @@ -465,11 +491,16 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date(), userId={}) { }; /** - * Returns the expiration time of the primary key or Infinity if key does not expire + * Returns the latest date when the key can be used for encrypting, signing, or both, depending on the `capabilities` paramater. + * When `capabilities` is null, defaults to returning the expiry date of the primary key. + * Returns Infinity if the key doesn't expire. + * @param {encrypt|sign|encrypt_sign} capabilities, optional + * @param {module:type/keyid} keyId, optional + * @param {Object} userId, optional user ID * @returns {Promise} * @async */ -Key.prototype.getExpirationTime = async function() { +Key.prototype.getExpirationTime = async function(capabilities, keyId, userId) { if (this.primaryKey.version === 3) { return getExpirationTime(this.primaryKey); } @@ -478,7 +509,16 @@ Key.prototype.getExpirationTime = async function() { const selfCert = primaryUser.selfCertification; const keyExpiry = getExpirationTime(this.primaryKey, selfCert); const sigExpiry = selfCert.getExpirationTime(); - return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; + let expiry = keyExpiry < sigExpiry ? keyExpiry : sigExpiry; + if (capabilities === 'encrypt' || capabilities === 'encrypt_sign') { + const encryptExpiry = (await this.getEncryptionKey(keyId, null, userId)).getExpirationTime(); + if (encryptExpiry < expiry) expiry = encryptExpiry; + } + if (capabilities === 'sign' || capabilities === 'encrypt_sign') { + const signExpiry = (await this.getSigningKey(keyId, null, userId)).getExpirationTime(); + if (signExpiry < expiry) expiry = signExpiry; + } + return expiry; } }; diff --git a/test/general/key.js b/test/general/key.js index 1e7991be..ac6c7f1d 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1248,6 +1248,62 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + '=REGo\n' + '-----END PGP PUBLIC KEY BLOCK-----\n'; +const priv_key_2000_2008 = `-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +t/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU= +=C0fJ +-----END PGP PRIVATE KEY BLOCK-----`; + it('Parsing armored text with two keys', function(done) { const pubKeys = openpgp.key.readArmored(twoKeys); @@ -1387,6 +1443,16 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); }); + it('Method getExpirationTime V4 Key with capabilities', async function() { + const pubKey = openpgp.key.readArmored(priv_key_2000_2008).keys[0]; + expect(pubKey).to.exist; + expect(pubKey).to.be.an.instanceof(openpgp.key.Key); + const expirationTime = await pubKey.getExpirationTime(); + expect(expirationTime).to.equal(Infinity); + const encryptExpirationTime = await pubKey.getExpirationTime('encrypt_sign'); + expect(encryptExpirationTime.toISOString()).to.equal('2008-02-12T17:12:08.000Z'); + }); + it('update() - throw error if fingerprints not equal', function(done) { const keys = openpgp.key.readArmored(twoKeys).keys; expect(keys[0].update.bind(