From a3484c31169e3389b85405bc9f43557ccc6b6693 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Fri, 23 Feb 2018 21:54:02 +0100 Subject: [PATCH] Key revocation --- src/enums.js | 17 +++++++++ src/key.js | 43 ++++++++++++++++++++-- test/general/key.js | 89 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/src/enums.js b/src/enums.js index 8e152b08..f7ef4652 100644 --- a/src/enums.js +++ b/src/enums.js @@ -428,6 +428,23 @@ export default { signature: 6 }, + /** {@link https://tools.ietf.org/html/rfc4880#section-5.2.3.23|RFC4880, section 5.2.3.23} + * @enum {Integer} + * @readonly + */ + reasonForRevocation: { + /** No reason specified (key revocations or cert revocations) */ + no_reason: 0, + /** Key is superseded (key revocations) */ + key_superseded: 1, + /** Key material has been compromised (key revocations) */ + key_compromised: 2, + /** Key is retired and no longer used (key revocations) */ + key_retired: 3, + /** User ID information is no longer valid (cert revocations) */ + userid_invalid: 32 + }, + /** {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.2.3.25|RFC4880bis-04, section 5.2.3.25} * @enum {Integer} * @readonly diff --git a/src/key.js b/src/key.js index d302c1eb..bb9dab1d 100644 --- a/src/key.js +++ b/src/key.js @@ -624,9 +624,45 @@ async function mergeSignatures(source, dest, attr, checkFn) { } } -// TODO -Key.prototype.revoke = function() { - +/** + * Revokes the key + * @param {module:key~Key} privateKey decrypted private key for revocation + * @param {Object} reasonForRevocation optional, object indicating the reason for revocation + * @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation + * @param {String} reasonForRevocation.string optional, string explaining the reason for revocation + * @param {Date} date optional, override the creationtime of the revocation signature + * @return {module:key~Key} new key with revocation signature + */ +Key.prototype.revoke = async function(privateKey, { + flag: reasonForRevocationFlag=enums.reasonForRevocation.no_reason, + string: reasonForRevocationString='' +} = {}, date=new Date()) { + if (privateKey.isPublic()) { + throw new Error('Need private key for revoking'); + } + if (privateKey.primaryKey.getFingerprint() !== this.primaryKey.getFingerprint()) { + throw new Error('Private key does not match public key'); + } + await privateKey.verifyPrimaryUser(); + const signingKeyPacket = privateKey.getSigningKeyPacket(); + if (!signingKeyPacket) { + 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.'); + } + const dataToSign = { key: this.primaryKey }; + const signaturePacket = new packet.Signature(date); + signaturePacket.signatureType = enums.write(enums.signature, enums.signature.key_revocation); + signaturePacket.reasonForRevocationFlag = enums.write(enums.reasonForRevocation, reasonForRevocationFlag); + signaturePacket.reasonForRevocationString = reasonForRevocationString; + signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); + await signaturePacket.sign(signingKeyPacket, dataToSign); + const key = new Key(this.toPacketlist()); + key.revocationSignature = signaturePacket; + return key; }; /** @@ -768,7 +804,6 @@ User.prototype.sign = async function(primaryKey, privateKeys) { signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey); - signaturePacket.signingKeyId = signingKeyPacket.getKeyId(); signaturePacket.sign(signingKeyPacket, dataToSign); return signaturePacket; })); diff --git a/test/general/key.js b/test/general/key.js index 463d89fb..e57479a9 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -50,6 +50,71 @@ describe('Key', function() { }); function tests() { + const priv_key_arm2 = + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + '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', + '=lw5e', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + + const pub_key_arm2 = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + 'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+', + 'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5', + 'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0', + 'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS', + 'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6', + 'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki', + 'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf', + '9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa', + 'JaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag', + 'Qg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr', + 'woBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb', + 'LgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA', + 'SF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP', + 'GLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2', + 'bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X', + 'W748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD', + 'AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY', + 'hz3tYjKhoFTKEIq3y3Pp', + '=h/aX', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + const twoKeys = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -1172,6 +1237,11 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + done(); }); + it('Verify status of revoked primary key', function(done) { + const pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; + expect(pubKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); + }); + it('Verify status of revoked subkey', function(done) { const pubKeys = openpgp.key.readArmored(pub_sig_test); expect(pubKeys).to.exist; @@ -1359,6 +1429,25 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' + }); }); + it('revoke() - primary key', function(done) { + const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; + const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; + privKey.decrypt('hello world'); + + pubKey.revoke(privKey, { + flag: openpgp.enums.reasonForRevocation.key_retired, + string: 'Testing key revocation' + }).then(revKey => { + expect(revKey.revocationSignature).to.exist; + expect(revKey.revocationSignature.signatureType).to.equal(openpgp.enums.signature.key_revocation); + expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_retired); + expect(revKey.revocationSignature.reasonForRevocationString).to.equal('Testing key revocation'); + + expect(pubKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.valid); + expect(revKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); + }); + }); + it("getPreferredAlgo('symmetric') - one key - AES256", async function() { const key1 = openpgp.key.readArmored(twoKeys).keys[0]; const prefAlgo = await openpgp.key.getPreferredAlgo('symmetric', [key1]);