From a3484c31169e3389b85405bc9f43557ccc6b6693 Mon Sep 17 00:00:00 2001
From: Daniel Huigens <d.huigens@protonmail.com>
Date: Fri, 23 Feb 2018 21:54:02 +0100
Subject: [PATCH 1/5] 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)',
+      '',
+      'lQH+BFJhL04BBADclrUEDDsm0PSZbQ6pml9FpzTyXiyCyDN+rMOsy9J300Oc10kt',
+      '/nyBej9vZSRcaW5VpNNj0iA+c1/w2FPf84zNsTzvDmuMaNHFUzky4/vkYuZra//3',
+      '+Ri7CF8RawSYQ/4IRbC9zqdBlzniyfQOW7Dp/LYe8eibnDSrmkQem0G0jwARAQAB',
+      '/gMDAu7L//czBpE40p1ZqO8K3k7UejemjsQqc7kOqnlDYd1Z6/3NEA/UM30Siipr',
+      'KjdIFY5+hp0hcs6EiiNq0PDfm/W2j+7HfrZ5kpeQVxDek4irezYZrl7JS2xezaLv',
+      'k0Fv/6fxasnFtjOM6Qbstu67s5Gpl9y06ZxbP3VpT62+Xeibn/swWrfiJjuGEEhM',
+      'bgnsMpHtzAz/L8y6KSzViG/05hBaqrvk3/GeEA6nE+o0+0a6r0LYLTemmq6FbaA1',
+      'PHo+x7k7oFcBFUUeSzgx78GckuPwqr2mNfeF+IuSRnrlpZl3kcbHASPAOfEkyMXS',
+      'sWGE7grCAjbyQyM3OEXTSyqnehvGS/1RdB6kDDxGwgE/QFbwNyEh6K4eaaAThW2j',
+      'IEEI0WEnRkPi9fXyxhFsCLSI1XhqTaq7iDNqJTxE+AX2b9ZuZXAxI3Tc/7++vEyL',
+      '3p18N/MB2kt1Wb1azmXWL2EKlT1BZ5yDaJuBQ8BhphM3tCRUZXN0IE1jVGVzdGlu',
+      'Z3RvbiA8dGVzdEBleGFtcGxlLmNvbT6IuQQTAQIAIwUCUmEvTgIbLwcLCQgHAwIB',
+      'BhUIAgkKCwQWAgMBAh4BAheAAAoJEEpjYTpNbkCUMAwD+gIK08qpEZSVas9qW+Ok',
+      '32wzNkwxe6PQgZwcyBqMQYZUcKagC8+89pMQQ5sKUGvpIgat42Tf1KLGPcvG4cDA',
+      'JZ6w2PYz9YHQqPh9LA+PAnV8m25TcGmKcKgvFUqQ3U53X/Y9sBP8HooRqfwwHcv9',
+      'pMgQmojmNbI4VHydRqIBePawnQH+BFJhL04BBADpH8+0EVolpPiOrXTKoBKTiyrB',
+      'UyxzodyJ8zmVJ3HMTEU/vidpQwzISwoc/ndDFMXQauq6xqBCD9m2BPQI3UdQzXnb',
+      'LsAI52nWCIqOkzM5NAKWoKhyXK9Y4UH4v9LAYQgl/stIISvCgG4mJ8lzzEBWvRdf',
+      'Qm2Ghb64/3V5NDdemwARAQAB/gMDAu7L//czBpE40iPcpLzL7GwBbWFhSWgSLy53',
+      'Md99Kxw3cApWCok2E8R9/4VS0490xKZIa5y2I/K8thVhqk96Z8Kbt7MRMC1WLHgC',
+      'qJvkeQCI6PrFM0PUIPLHAQtDJYKtaLXxYuexcAdKzZj3FHdtLNWCooK6n3vJlL1c',
+      'WjZcHJ1PH7USlj1jup4XfxsbziuysRUSyXkjn92GZLm+64vCIiwhqAYoizF2NHHG',
+      'hRTN4gQzxrxgkeVchl+ag7DkQUDANIIVI+A63JeLJgWJiH1fbYlwESByHW+zBFNt',
+      'qStjfIOhjrfNIc3RvsggbDdWQLcbxmLZj4sB0ydPSgRKoaUdRHJY0S4vp9ouKOtl',
+      '2au/P1BP3bhD0fDXl91oeheYth+MSmsJFDg/vZJzCJhFaQ9dp+2EnjN5auNCNbaI',
+      'beFJRHFf9cha8p3hh+AK54NRCT++B2MXYf+TPwqX88jYMBv8kk8vYUgo8128r1zQ',
+      'EzjviQE9BBgBAgAJBQJSYS9OAhsuAKgJEEpjYTpNbkCUnSAEGQECAAYFAlJhL04A',
+      'CgkQ4IT3RGwgLJe6ogQA2aaJEIBIXtgrs+8WSJ4k3DN4rRXcXaUZf667pjdD9nF2',
+      '3BzjFH6Z78JIGaxRHJdM7b05aE8YuzM8f3NIlT0F0OLq/TI2muYU9f/U2DQBuf+w',
+      'KTB62+PELVgi9MsXC1Qv/u/o1LZtmmxTFFOD35xKsxZZI2OJj2pQpqObW27M8Nlc',
+      'BQQAw2YA3fFc38qPK+PY4rZyTRdbvjyyX+1zeqIo8wn7QCQwXs+OGaH2fGoT35AI',
+      'SXuqKcWqoEuO7OBSEFThCXBfUYMC01OrqKEswPm/V3zZkLu01q12UMwZach28QwK',
+      '/YZly4ioND2tdazj17u2rU2dwtiHPe1iMqGgVMoQirfLc+k=',
+      '=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]);

From 368d80245a384063bbd2c71f7169f591a9fc1ea1 Mon Sep 17 00:00:00 2001
From: Daniel Huigens <d.huigens@protonmail.com>
Date: Sat, 24 Feb 2018 18:32:04 +0100
Subject: [PATCH 2/5] Subkey revocation

---
 src/key.js          | 43 +++++++++++++++++++++++++++++++++++++++++++
 test/general/key.js | 19 +++++++++++++++++++
 2 files changed, 62 insertions(+)

diff --git a/src/key.js b/src/key.js
index bb9dab1d..118ed8ce 100644
--- a/src/key.js
+++ b/src/key.js
@@ -1070,6 +1070,49 @@ SubKey.prototype.update = async function(subKey, primaryKey) {
   });
 };
 
+/**
+ * Revokes the subkey
+ * @param  {module:packet/signature} primaryKey primary key used for revocation
+ * @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~SubKey} new subkey with revocation signature
+ */
+SubKey.prototype.revoke = async function(primaryKey, 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() !== 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: primaryKey, bind: this.subKey };
+  const signaturePacket = new packet.Signature(date);
+  signaturePacket.signatureType = enums.write(enums.signature, enums.signature.subkey_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 subKey = new SubKey(this.subKey);
+  subKey.revocationSignature = signaturePacket;
+  await subKey.update(this, primaryKey);
+  return subKey;
+};
+
 /**
  * Reads an unarmored OpenPGP key list and returns one or multiple key objects
  * @param {Uint8Array} data to be parsed
diff --git a/test/general/key.js b/test/general/key.js
index e57479a9..4ee5970e 100644
--- a/test/general/key.js
+++ b/test/general/key.js
@@ -1448,6 +1448,25 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
     });
   });
 
+  it('revoke() - subkey', 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');
+
+    const subKey = pubKey.subKeys[0];
+    subKey.revoke(pubKey.primaryKey, privKey, {
+      flag: openpgp.enums.reasonForRevocation.key_superseded
+    }).then(revKey => {
+      expect(revKey.revocationSignature).to.exist;
+      expect(revKey.revocationSignature.signatureType).to.equal(openpgp.enums.signature.subkey_revocation);
+      expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_superseded);
+      expect(revKey.revocationSignature.reasonForRevocationString).to.equal('');
+
+      expect(subKey.verify(pubKey.primaryKey)).to.eventually.equal(openpgp.enums.keyStatus.valid);
+      expect(revKey.verify(pubKey.primaryKey)).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]);

From 1ed7943bf93cdd5801c84bceb9d26291c2eea295 Mon Sep 17 00:00:00 2001
From: Daniel Huigens <d.huigens@protonmail.com>
Date: Tue, 27 Feb 2018 22:55:06 +0100
Subject: [PATCH 3/5] Create openpgp.revokeKey

---
 README.md               |  24 +++++++
 src/index.js            |   2 +-
 src/key.js              |  65 +++++++++++++++++
 src/openpgp.js          |  97 +++++++++++++++++++++-----
 test/general/key.js     | 150 ++++++++++++++++++++++++++++++++++++++++
 test/general/openpgp.js |  36 ++++++++++
 6 files changed, 357 insertions(+), 17 deletions(-)

diff --git a/README.md b/README.md
index a62a6641..92bbfb61 100644
--- a/README.md
+++ b/README.md
@@ -240,6 +240,30 @@ var options = {
 openpgp.generateKey(options).then(function(key) {
     var privkey = key.privateKeyArmored; // '-----BEGIN PGP PRIVATE KEY BLOCK ... '
     var pubkey = key.publicKeyArmored;   // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
+    var revocationSignature = key.revocationSignature; // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
+});
+```
+
+#### Revoke a key
+
+Using a revocation signature:
+```js
+var options = {
+    key: openpgp.key.readArmored(pubkey).keys[0],
+    revocationSignature: revocationSignature
+};
+```
+
+Using the private key:
+```js
+var options = {
+    key: openpgp.key.readArmored(privkey).keys[0]
+};
+```
+
+```js
+openpgp.revokeKey(options).then(function(key) {
+    var pubkey = key.publicKeyArmored;   // '-----BEGIN PGP PUBLIC KEY BLOCK ... '
 });
 ```
 
diff --git a/src/index.js b/src/index.js
index 7b2b7f14..e0cd2f3c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -19,7 +19,7 @@ export default openpgp;
  */
 export {
   encrypt, decrypt, sign, verify,
-  generateKey, reformatKey, decryptKey,
+  generateKey, reformatKey, revokeKey, decryptKey,
   encryptSessionKey, decryptSessionKeys,
   initWorker, getWorker, destroyWorker
 } from './openpgp';
diff --git a/src/key.js b/src/key.js
index 118ed8ce..8713cfac 100644
--- a/src/key.js
+++ b/src/key.js
@@ -665,6 +665,56 @@ Key.prototype.revoke = async function(privateKey, {
   return key;
 };
 
+/**
+ * Get revocation certificate from a revoked key.
+ *   (To get a revocation certificate for an unrevoked key, call revoke() first.)
+ * @return {String} armored revocation certificate
+ */
+Key.prototype.getRevocationCertificate = function() {
+  if (this.revocationSignature) {
+    const commentstring = config.commentstring;
+    config.commentstring = 'This is a revocation certificate';
+    try {
+      const packetlist = new packet.List();
+      packetlist.push(this.revocationSignature);
+      return armor.encode(enums.armor.public_key, packetlist.write());
+    } finally {
+      // Restore comment string. armor.encode() shouldn't throw, but just to be sure it's wrapped in a try/finally
+      config.commentstring = commentstring;
+    }
+  }
+};
+
+/**
+ * Applies a revocation certificate to a key
+ * @param  {String} revocationCertificate armored revocation certificate
+ * @return {module:key~Key} new revoked key
+ */
+Key.prototype.applyRevocationCertificate = async function(revocationCertificate) {
+  const input = armor.decode(revocationCertificate);
+  if (input.type !== enums.armor.public_key) {
+    throw new Error('Armored text not of type public key');
+  }
+  const packetlist = new packet.List();
+  packetlist.read(input.data);
+  const revocationSignature = packetlist.findPacket(enums.packet.signature);
+  if (!revocationSignature || revocationSignature.signatureType !== enums.signature.key_revocation) {
+    throw new Error('Could not find revocation signature packet');
+  }
+  if (!revocationSignature.issuerKeyId.equals(this.primaryKey.getKeyId())) {
+    throw new Error('Revocation signature does not match key');
+  }
+  if (revocationSignature.isExpired()) {
+    throw new Error('Revocation signature is expired');
+  }
+  if (!await revocationSignature.verify(this.primaryKey, { key: this.primaryKey })) {
+    throw new Error('Could not verify revocation signature');
+  }
+  const key = new Key(this.toPacketlist());
+  key.revocationSignature = revocationSignature;
+  return key;
+};
+
 /**
  * Signs primary user of key
  * @param  {Array<module:key.Key>} privateKey decrypted private keys for signing
@@ -1185,6 +1235,7 @@ export function readArmored(armoredText) {
  * @param  {Date} date         Override the creation date of the key and the key signatures
  * @param  {Array<Object>} subkeys   (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
  *                                              sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
+ * @param {Boolean} [options.revoked=false] Whether the key should include a revocation signature
  * @returns {Promise<module:key.Key>}
  * @async
  * @static
@@ -1266,6 +1317,7 @@ export async function generate(options) {
  * @param  {Date} date         Override the creation date of the key and the key signatures
  * @param  {Array<Object>} subkeys   (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
  *
+ * @param {Boolean} [options.revoked=false] Whether the key should include a revocation signature
  * @returns {Promise<module:key.Key>}
  * @async
  * @static
@@ -1416,6 +1468,19 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) {
     });
   });
 
+  if (options.revoked) {
+    const dataToSign = {};
+    dataToSign.key = secretKeyPacket;
+    const revocationSignaturePacket = new packet.Signature();
+    revocationSignaturePacket.signatureType = enums.signature.key_revocation;
+    revocationSignaturePacket.reasonForRevocationFlag = enums.reasonForRevocation.no_reason;
+    revocationSignaturePacket.reasonForRevocationString = '';
+    revocationSignaturePacket.publicKeyAlgorithm = options.keyType;
+    revocationSignaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket);
+    await revocationSignaturePacket.sign(secretKeyPacket, dataToSign);
+    packetlist.push(revocationSignaturePacket);
+  }
+
   // set passphrase protection
   if (options.passphrase) {
     secretKeyPacket.clearPrivateParams();
diff --git a/src/openpgp.js b/src/openpgp.js
index 7bc4706e..37c279a1 100644
--- a/src/openpgp.js
+++ b/src/openpgp.js
@@ -108,15 +108,16 @@ export function destroyWorker() {
  * @param  {Date} date               (optional) override the creation date of the key and the key signatures
  * @param  {Array<Object>} subkeys   (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
  *                                              sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
+ * @param  {Boolean} revocationCertificate (optional) Whether the returned object should include a revocation certificate to revoke the public key
  * @returns {Promise<Object>}         The generated key object in the form:
- *                                     { key:Key, privateKeyArmored:String, publicKeyArmored:String }
+ *                                     { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String }
  * @async
  * @static
  */
 
-export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}] }) {
+export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}], revocationCertificate=true }) {
   userIds = toArray(userIds);
-  const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys };
+  const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys, revocationCertificate };
   if (util.getWebCryptoAll() && numBits < 2048) {
     throw new Error('numBits should be 2048 or 4096, found: ' + numBits);
   }
@@ -125,13 +126,21 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
     return asyncProxy.delegate('generateKey', options);
   }
 
-  return generate(options).then(key => ({
+  options.revoked = options.revocationCertificate;
 
-    key: key,
-    privateKeyArmored: key.armor(),
-    publicKeyArmored: key.toPublic().armor()
+  return generate(options).then(key => {
+    const revocationCertificate = key.getRevocationCertificate();
+    key.revocationSignature = null;
 
-  })).catch(onError.bind(null, 'Error generating keypair'));
+    return {
+
+      key: key,
+      privateKeyArmored: key.armor(),
+      publicKeyArmored: key.toPublic().armor(),
+      revocationCertificate: revocationCertificate
+
+    };
+  }).catch(onError.bind(null, 'Error generating keypair'));
 }
 
 /**
@@ -140,25 +149,81 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
  * @param  {Array<Object>} userIds   array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }]
  * @param  {String} passphrase       (optional) The passphrase used to encrypt the resulting private key
  * @param  {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires
+ * @param  {Boolean} revocationCertificate (optional) Whether the returned object should include a revocation certificate to revoke the public key
  * @returns {Promise<Object>}         The generated key object in the form:
- *                                     { key:Key, privateKeyArmored:String, publicKeyArmored:String }
+ *                                     { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String }
  * @async
  * @static
  */
-export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date}) {
+export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date, revocationCertificate=true}) {
   userIds = toArray(userIds);
-  const options = { privateKey, userIds, passphrase, keyExpirationTime, date};
+  const options = { privateKey, userIds, passphrase, keyExpirationTime, date, revocationCertificate=true};
   if (asyncProxy) {
     return asyncProxy.delegate('reformatKey', options);
   }
 
-  return reformat(options).then(key => ({
+  options.revoked = options.revocationCertificate;
 
-    key: key,
-    privateKeyArmored: key.armor(),
-    publicKeyArmored: key.toPublic().armor()
+  return reformat(options).then(key => {
+    const revocationCertificate = key.getRevocationCertificate();
+    key.revocationSignature = null;
 
-  })).catch(onError.bind(null, 'Error reformatting keypair'));
+    return {
+
+      key: key,
+      privateKeyArmored: key.armor(),
+      publicKeyArmored: key.toPublic().armor(),
+      revocationCertificate: revocationCertificate
+
+    };
+  }).catch(onError.bind(null, 'Error reformatting keypair'));
+}
+
+/**
+ * Revokes a key. Requires either a private key or a revocation certificate.
+ *   If a revocation certificate is passed, the reasonForRevocation parameters will be ignored.
+ * @param  {Key} key                 (optional) public or private key to revoke
+ * @param  {String} revocationCertificate (optional) revocation certificate to revoke the key with
+ * @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
+ * @return {Promise<Object>}         The revoked key object in the form:
+ *                                     { privateKey:Key, privateKeyArmored:String, publicKey:Key, publicKeyArmored:String }
+ *                                     (if private key is passed) or { publicKey:Key, publicKeyArmored:String } (otherwise)
+ * @static
+ */
+export function revokeKey({
+  key, revocationCertificate, reasonForRevocation
+} = {}) {
+  const options = {
+    key, revocationCertificate, reasonForRevocation
+  };
+
+  if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported
+    return asyncProxy.delegate('revokeKey', options);
+  }
+
+  return Promise.resolve().then(() => {
+    if (revocationCertificate) {
+      return key.applyRevocationCertificate(revocationCertificate);
+    } else {
+      return key.revoke(key, reasonForRevocation);
+    }
+  }).then(key => {
+    if(key.isPrivate()) {
+      const publicKey = key.toPublic();
+      return {
+        privateKey: key,
+        privateKeyArmored: key.armor(),
+        publicKey: publicKey,
+        publicKeyArmored: publicKey.armor()
+      };
+    }
+    return {
+      publicKey: key,
+      publicKeyArmored: key.armor()
+    };
+  }).catch(onError.bind(null, 'Error revoking key'));
 }
 
 /**
diff --git a/test/general/key.js b/test/general/key.js
index 4ee5970e..4d6f319f 100644
--- a/test/general/key.js
+++ b/test/general/key.js
@@ -114,6 +114,97 @@ function tests() {
       'hz3tYjKhoFTKEIq3y3Pp',
       '=h/aX',
       '-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
+      
+  const pub_key_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG 2.1.15 (GNU/Linux)
+
+mI0EWpoNLAEEAMoO8dfnLvvCze1hjWcr8t1fMdndFQc1fAM7dm6sbqrdlaAz+Dab
+zF3F9UhIOCcABRm+QHyZlgEsoQpHF/7sWflUK1FpoxdORINtIDilukUkMZ0NnIaD
++8pRutdSczPNFvSImSzZNCyLzvDCGMO3+Xeaa6pViSPEeBwhXWJUuHYtABEBAAG0
+IkpvZSBVc2VyIDxqb2UudXNlckBwcm90b25tYWlsLmNvbT6ItwQTAQgAIQUCWpoN
+LAIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRBYtrN4WUnCtoUjBACi6qVb
+noQJ1aOaOyoBZscDIO5XZHZK4L9V9uJJhD9v1qRF5s0PRiG969EwFlvjqIlLiRej
+KSxvE/1rcym4GwBUndku1fMM+weTWHNtRn9+BPzN/4eKZbUbY3fHtlk+Lde3N+CZ
+vrGKS/ICtbtuAfZL0LdzzqnNuBUXlO6EpG5C3riNBFqaDSwBBADDURzGkpTn/FTT
+xHyheai+zTOUmy7N1ViCRPkErIeD606tZf/sKqAnEChfECeZJReYydN1B3O8QOyI
+Ly/rH0DS2bt/6juhknPVGHPUAyNxHmiHYXTUgGPEX1QfusjzBcfIk6vHjYBiRm/I
+u9iwrzCwypA4dWDZSTZuFrVsf4n+twARAQABiJ8EGAEIAAkFAlqaDSwCGwwACgkQ
+WLazeFlJwrZQEAQAuUrp9Qp76CnKqUsUjcVxq7DJBi/lewyGGYSVAFt6/0Xyg/8Y
+TEa/c4Dri/HMOtrfbgjp/doIVaZLOXZYfqRcpy3z0M6BierOPB3D+fdaTfd7gIrQ
+nGHIp2NmbJZnYgl8Ps23qF+LKTa1eE+AmMQYzUHSGuka2lp6OglwWzg/dEw=
+=/vbH
+-----END PGP PUBLIC KEY BLOCK-----`;
+
+  const priv_key_arm4 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG 2.1.15 (GNU/Linux)
+
+lQIGBFqaDSwBBADKDvHX5y77ws3tYY1nK/LdXzHZ3RUHNXwDO3ZurG6q3ZWgM/g2
+m8xdxfVISDgnAAUZvkB8mZYBLKEKRxf+7Fn5VCtRaaMXTkSDbSA4pbpFJDGdDZyG
+g/vKUbrXUnMzzRb0iJks2TQsi87wwhjDt/l3mmuqVYkjxHgcIV1iVLh2LQARAQAB
+/gcDAoZ8RULY7umS4fVGPmTuETCnOOTGancXT5r7chKyfFXlyVU4ULvTdLwdFtqx
+Vl9tNyED31nIiRP1CTmZLeaVScNGfVLjo8nvpMZUVopw5UdaFADeVTpwVdtp7ru+
+IgH4ynrRMgMGh7/dgBzIP8WN4w8uBPK5G4bS34NNiREkVoZ3oh4dA/6aeYfW7lVV
+cYRl2F7++AGfqS+FpLsE8KjFU2z8POJjWMN1nYKwjNa+beEO0BFYdUFvMzU7eUHA
+/G0xWAhYvNyuJHE4imgYmCy1OZeawc9h8YGeaQJCh2NTVzaD9HRu0xmz93bNF19q
+bfUZJC7mC6WzKsRXHX0JmzH+9DShUqGnkRl5fMo2UhQMpSxsMT4dU/Ji4q+t96oy
+K6g3DMJr5OtZML3XKxGmdy0CgepkG1aikrC9qLfBgxjqi+uTewcbrS9lAOfrZg4N
+jwt1FLEK8gu7aOeczdW/pFOHOCrX1DnpF81JKJ1a7hz5JRP1m+ffqwm0IkpvZSBV
+c2VyIDxqb2UudXNlckBwcm90b25tYWlsLmNvbT6ItwQTAQgAIQUCWpoNLAIbAwUL
+CQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRBYtrN4WUnCtoUjBACi6qVbnoQJ1aOa
+OyoBZscDIO5XZHZK4L9V9uJJhD9v1qRF5s0PRiG969EwFlvjqIlLiRejKSxvE/1r
+cym4GwBUndku1fMM+weTWHNtRn9+BPzN/4eKZbUbY3fHtlk+Lde3N+CZvrGKS/IC
+tbtuAfZL0LdzzqnNuBUXlO6EpG5C3p0CBgRamg0sAQQAw1EcxpKU5/xU08R8oXmo
+vs0zlJsuzdVYgkT5BKyHg+tOrWX/7CqgJxAoXxAnmSUXmMnTdQdzvEDsiC8v6x9A
+0tm7f+o7oZJz1Rhz1AMjcR5oh2F01IBjxF9UH7rI8wXHyJOrx42AYkZvyLvYsK8w
+sMqQOHVg2Uk2bha1bH+J/rcAEQEAAf4HAwLwNvRIoBFS3OHTIYirkr4sHzSkWFJx
+xDPozovXgCq7BoCXDMaSIQLwZqEfb+SabYtk7nLSnG2Y2mgwb9swZuBZEWuQjZk7
+lX1MvuZ0Ih2QdQSMEJk8sEsMoBGHHdHh/MZO4a27+5B9OceDfnEZZcGSOweUuu1n
+IlgWcgrM40q4S3Mt39FXFgdJWnpd93hAokKDHklUGMdMLw/02dGVRkJmvUp9qdhe
+c2njq9HSeYwqbY2rYgcNsF2ZcCLt9UXA2dOG4X2c2mPfjKuTRZUPxNKh6JfL3mlu
+rBdd/z8gQHoKObyaarVwN3HAbtP0+6Z8a9/wDYj1K9ZCoHuEtKq1qq5J2Ec8+Yzl
+K0Zlcs760LiYUr69CninMrnbDNnAhrYAcyJS42viUADPv9g+CBbyanB4KyE4UNrZ
+BCB296lOEW4v1IZVNrNvqrbka3/p0qqBJiFTh7eT3zXpRNArFZDmLCUEEm53qT1a
+PO/MyYUGTTMRAzTmNTiPiJ8EGAEIAAkFAlqaDSwCGwwACgkQWLazeFlJwrZQEAQA
+uUrp9Qp76CnKqUsUjcVxq7DJBi/lewyGGYSVAFt6/0Xyg/8YTEa/c4Dri/HMOtrf
+bgjp/doIVaZLOXZYfqRcpy3z0M6BierOPB3D+fdaTfd7gIrQnGHIp2NmbJZnYgl8
+Ps23qF+LKTa1eE+AmMQYzUHSGuka2lp6OglwWzg/dEw=
+=mr3M
+-----END PGP PRIVATE KEY BLOCK-----`;
+
+  const revocation_certificate_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG 2.1.15 (GNU/Linux)
+Comment: This is a revocation certificate
+
+iJ8EIAEIAAkFAlqaDT0CHQAACgkQWLazeFlJwrbOaAP/V38FhBrUy4XYgt8ZX22G
+ov6IFDNoyRKafSuz7Rg+8K8cf+0MAsSi52ueKfsbPxQ+I1vPeaEuEYbwTjtbvM+M
+vZcX+VNYdsc1iZeNaT4ayA+2LrCN/xgFj0nrExHqcZAjgBZ9pvKghAqdK4Zb2Ghb
+7chPiLLNWJCMtL4bo7a1X84=
+=HcWg
+-----END PGP PUBLIC KEY BLOCK-----`;
+
+  const revoked_key_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG 2.1.15 (GNU/Linux)
+
+mI0EWpoNLAEEAMoO8dfnLvvCze1hjWcr8t1fMdndFQc1fAM7dm6sbqrdlaAz+Dab
+zF3F9UhIOCcABRm+QHyZlgEsoQpHF/7sWflUK1FpoxdORINtIDilukUkMZ0NnIaD
++8pRutdSczPNFvSImSzZNCyLzvDCGMO3+Xeaa6pViSPEeBwhXWJUuHYtABEBAAGI
+nwQgAQgACQUCWpoNPQIdAAAKCRBYtrN4WUnCts5oA/9XfwWEGtTLhdiC3xlfbYai
+/ogUM2jJEpp9K7PtGD7wrxx/7QwCxKLna54p+xs/FD4jW895oS4RhvBOO1u8z4y9
+lxf5U1h2xzWJl41pPhrID7YusI3/GAWPSesTEepxkCOAFn2m8qCECp0rhlvYaFvt
+yE+Iss1YkIy0vhujtrVfzrQiSm9lIFVzZXIgPGpvZS51c2VyQHByb3Rvbm1haWwu
+Y29tPoi3BBMBCAAhBQJamg0sAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJ
+EFi2s3hZScK2hSMEAKLqpVuehAnVo5o7KgFmxwMg7ldkdkrgv1X24kmEP2/WpEXm
+zQ9GIb3r0TAWW+OoiUuJF6MpLG8T/WtzKbgbAFSd2S7V8wz7B5NYc21Gf34E/M3/
+h4pltRtjd8e2WT4t17c34Jm+sYpL8gK1u24B9kvQt3POqc24FReU7oSkbkLeuI0E
+WpoNLAEEAMNRHMaSlOf8VNPEfKF5qL7NM5SbLs3VWIJE+QSsh4PrTq1l/+wqoCcQ
+KF8QJ5klF5jJ03UHc7xA7IgvL+sfQNLZu3/qO6GSc9UYc9QDI3EeaIdhdNSAY8Rf
+VB+6yPMFx8iTq8eNgGJGb8i72LCvMLDKkDh1YNlJNm4WtWx/if63ABEBAAGInwQY
+AQgACQUCWpoNLAIbDAAKCRBYtrN4WUnCtlAQBAC5Sun1CnvoKcqpSxSNxXGrsMkG
+L+V7DIYZhJUAW3r/RfKD/xhMRr9zgOuL8cw62t9uCOn92ghVpks5dlh+pFynLfPQ
+zoGJ6s48HcP591pN93uAitCcYcinY2ZslmdiCXw+zbeoX4spNrV4T4CYxBjNQdIa
+6RraWno6CXBbOD90TA==
+=8d2d
+-----END PGP PUBLIC KEY BLOCK-----`;
 
   const twoKeys =
        ['-----BEGIN PGP PUBLIC KEY BLOCK-----',
@@ -1467,6 +1558,34 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
     });
   });
 
+  it('applyRevocationCertificate() should produce the same revoked key as GnuPG', function() {
+    const pubKey = openpgp.key.readArmored(pub_key_arm4).keys[0];
+
+    return pubKey.applyRevocationCertificate(revocation_certificate_arm4).then(revKey => {
+      expect(revKey.armor()).to.equal(openpgp.key.readArmored(revoked_key_arm4).keys[0].armor());
+    });
+  });
+
+  it('getRevocationCertificate() should produce the same revocation certificate as GnuPG', function() {
+    const revKey = openpgp.key.readArmored(revoked_key_arm4).keys[0];
+    const revocationCertificate = revKey.getRevocationCertificate();
+
+    const input = openpgp.armor.decode(revocation_certificate_arm4);
+    const packetlist = new openpgp.packet.List();
+    packetlist.read(input.data);
+    const armored = openpgp.armor.encode(openpgp.enums.armor.public_key, packetlist.write());
+
+    expect(revocationCertificate.replace(/^Comment: .*$/m, '')).to.equal(armored.replace(/^Comment: .*$/m, ''));
+  });
+
+  it('getRevocationCertificate() should have an appropriate comment', function() {
+    const revKey = openpgp.key.readArmored(revoked_key_arm4).keys[0];
+    const revocationCertificate = revKey.getRevocationCertificate();
+
+    expect(revocationCertificate).to.match(/Comment: This is a revocation certificate/);
+    expect(revKey.armor()).not.to.match(/Comment: This is a revocation certificate/);
+  });
+
   it("getPreferredAlgo('symmetric') - one key - AES256", async function() {
     const key1 = openpgp.key.readArmored(twoKeys).keys[0];
     const prefAlgo = await openpgp.key.getPreferredAlgo('symmetric', [key1]);
@@ -2012,6 +2131,37 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
     });
   });
 
+  it('Revoke generated key with revocation certificate', function() {
+    const opt = {numBits: 512, userIds: 'test1 <a@b.com>', passphrase: '1234'};
+    if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
+    return openpgp.generateKey(opt).then(function(original) {
+      return openpgp.revokeKey({key: original.key.toPublic(), revocationCertificate: original.revocationCertificate}).then(function(revKey) {
+        revKey = revKey.publicKey;
+        expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason);
+        expect(revKey.revocationSignature.reasonForRevocationString).to.equal('');
+        return revKey.verifyPrimaryKey().then(function(status) {
+          expect(status).to.equal(openpgp.enums.keyStatus.revoked);
+        });
+      });
+    });
+  });
+
+  it('Revoke generated key with private key', function() {
+    const opt = {numBits: 512, userIds: 'test1 <a@b.com>', passphrase: '1234'};
+    if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
+    return openpgp.generateKey(opt).then(function(original) {
+      original.key.decrypt('1234');
+      return openpgp.revokeKey({key: original.key, reasonForRevocation: {string: 'Testing key revocation'}}).then(function(revKey) {
+        revKey = revKey.publicKey;
+        expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason);
+        expect(revKey.revocationSignature.reasonForRevocationString).to.equal('Testing key revocation');
+        return revKey.verifyPrimaryKey().then(function(status) {
+          expect(status).to.equal(openpgp.enums.keyStatus.revoked);
+        });
+      });
+    });
+  });
+
   it('Merge key with another key with non-ID user attributes', function(done) {
     const key = openpgp.key.readArmored(mergeKey1).keys[0];
     const updateKey = openpgp.key.readArmored(mergeKey2).keys[0];
diff --git a/test/general/openpgp.js b/test/general/openpgp.js
index a94c676c..4b9f9aac 100644
--- a/test/general/openpgp.js
+++ b/test/general/openpgp.js
@@ -484,6 +484,10 @@ describe('OpenPGP.js public api tests', function() {
               return 'pub_key';
             }
           };
+        },
+        getRevocationCertificate: function() {},
+        removeRevocationCertificate: function() {
+          return this;
         }
       };
       keyGenStub = stub(openpgp.key, 'generate');
@@ -514,6 +518,8 @@ describe('OpenPGP.js public api tests', function() {
           curve: "",
           date: now,
           subkeys: [],
+          revocationCertificate: true,
+          revoked: true,
         }).calledOnce).to.be.true;
         expect(newKey.key).to.exist;
         expect(newKey.privateKeyArmored).to.exist;
@@ -1855,6 +1861,36 @@ describe('OpenPGP.js public api tests', function() {
                 expect(signatures[0].signature.packets.length).to.equal(1);
             });
         });
+
+        it.skip('should fail to encrypt with revoked key', function() {
+          return openpgp.revokeKey({
+            key: privateKey.keys[0]
+          }).then(function(revKey) {
+            return openpgp.encrypt({
+              data: plaintext,
+              publicKeys: revKey.publicKey
+            }).then(function(encrypted) {
+              throw new Error('Should not encrypt with revoked key');
+            }).catch(function(error) {
+              expect(error.message).to.match(/Could not find valid key packet for encryption/);
+            });
+          });
+        });
+
+        it.skip('should fail to encrypt with revoked subkey', function() {
+          let clonedKey = privateKey.keys[0].toPublic();
+          return clonedKey.subKeys[0].revoke(clonedKey.primaryKey, privateKey.keys[0]).then(function(revSubKey) {
+            clonedKey.subKeys[0] = revSubKey;
+            return openpgp.encrypt({
+              data: plaintext,
+              publicKeys: clonedKey
+            }).then(function(encrypted) {
+              throw new Error('Should not encrypt with revoked subkey');
+            }).catch(function(error) {
+              expect(error.message).to.match(/Could not find valid key packet for encryption/);
+            });
+          });
+        });
       });
 
       describe('ELG / DSA encrypt, decrypt, sign, verify', function() {

From e411839ae3bdec9c98b1bc094f946065bd67fa15 Mon Sep 17 00:00:00 2001
From: Daniel Huigens <d.huigens@protonmail.com>
Date: Fri, 2 Mar 2018 23:50:44 +0100
Subject: [PATCH 4/5] Deduplicate signature packet creation

---
 src/key.js     | 108 ++++++++++++++++++++++---------------------------
 src/message.js |  22 ++--------
 2 files changed, 51 insertions(+), 79 deletions(-)

diff --git a/src/key.js b/src/key.js
index 8713cfac..2649f659 100644
--- a/src/key.js
+++ b/src/key.js
@@ -637,31 +637,16 @@ 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;
+  key.revocationSignature = await createSignaturePacket(dataToSign, privateKey, {
+    signatureType: enums.signature.key_revocation,
+    reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
+    reasonForRevocationString
+  }, date);
   return key;
 };
 
@@ -833,29 +818,15 @@ User.prototype.toPacketlist = function() {
 User.prototype.sign = async function(primaryKey, privateKeys) {
   const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey };
   const user = new User(dataToSign.userid);
-  user.otherCertifications = await Promise.all(privateKeys.map(async function(privateKey) {
-    if (privateKey.isPublic()) {
-      throw new Error('Need private key for signing');
-    }
+  user.otherCertifications = await Promise.all(privateKeys.map(function(privateKey) {
     if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) {
       throw new Error('Not implemented for self signing');
     }
-    const signingKeyPacket = await 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 signaturePacket = new packet.Signature();
-    // Most OpenPGP implementations use generic certification (0x10)
-    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 = await getPreferredHashAlgo(privateKey);
-    signaturePacket.sign(signingKeyPacket, dataToSign);
-    return signaturePacket;
+    return createSignaturePacket(dataToSign, privateKey, {
+      // Most OpenPGP implementations use generic certification (0x10)
+      signatureType: enums.signature.cert_generic,
+      keyFlags: [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]
+    });
   }));
   await user.update(this, primaryKey);
   return user;
@@ -883,6 +854,38 @@ User.prototype.isRevoked = async function(primaryKey, certificate, key, date=new
   );
 };
 
+/**
+ * Create signature packet
+ * @param  {Object}                          dataToSign Contains packets to be signed
+ * @param  {module:key~Key}                  privateKey private key with decrypted secret key data for signing
+ * @param  {Object} signatureProperties      (optional) properties to write on the signature packet before signing
+ * @param  {Date} date                       (optional) override the creationtime of the signature
+ * @param  {Object} userId                   (optional) user ID
+ * @return {module:packet/signature}         signature packet
+ */
+export async function createSignaturePacket(dataToSign, privateKey, signatureProperties, date, userId) {
+  if (privateKey.isPublic()) {
+    throw new Error('Need private key for signing');
+  }
+  await privateKey.verifyPrimaryUser();
+  const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId);
+  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 signaturePacket = new packet.Signature(date);
+  for(const [prop, value] of Object.entries(signatureProperties)) {
+    signaturePacket[prop] = value;
+  }
+  signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
+  signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId);
+  await signaturePacket.sign(signingKeyPacket, dataToSign);
+  return signaturePacket;
+}
+
 /**
  * Verifies the user certificate
  * @param  {module:packet.SecretKey|
@@ -1134,31 +1137,16 @@ SubKey.prototype.revoke = async function(primaryKey, 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() !== 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: primaryKey, bind: this.subKey };
-  const signaturePacket = new packet.Signature(date);
-  signaturePacket.signatureType = enums.write(enums.signature, enums.signature.subkey_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 subKey = new SubKey(this.subKey);
-  subKey.revocationSignature = signaturePacket;
+  subKey.revocationSignature = await createSignaturePacket(dataToSign, privateKey, {
+    signatureType: enums.signature.subkey_revocation,
+    reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
+    reasonForRevocationString
+  }, date);
   await subKey.update(this, primaryKey);
   return subKey;
 };
diff --git a/src/message.js b/src/message.js
index 2c3826db..ffba8a94 100644
--- a/src/message.js
+++ b/src/message.js
@@ -36,7 +36,7 @@ import enums from './enums';
 import util from './util';
 import packet from './packet';
 import { Signature } from './signature';
-import { getPreferredHashAlgo, getPreferredAlgo, isAeadSupported } from './key';
+import { getPreferredHashAlgo, getPreferredAlgo, isAeadSupported, createSignaturePacket } from './key';
 
 
 /**
@@ -499,24 +499,8 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
   const signatureType = literalDataPacket.text === null ?
     enums.signature.binary : enums.signature.text;
 
-  await Promise.all(privateKeys.map(async function(privateKey) {
-    if (privateKey.isPublic()) {
-      throw new Error('Need private key for signing');
-    }
-    const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId);
-    if (!signingKeyPacket) {
-      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.');
-    }
-    const signaturePacket = new packet.Signature(date);
-    signaturePacket.signatureType = signatureType;
-    signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
-    signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId);
-    await signaturePacket.sign(signingKeyPacket, literalDataPacket);
-    return signaturePacket;
+  await Promise.all(privateKeys.map(privateKey => {
+    return createSignaturePacket(literalDataPacket, privateKey, {signatureType}, date, userId);
   })).then(signatureList => {
     signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
   });

From 3fd0fa8f6858e1dbdff9f07f3ed37fd995441aaf Mon Sep 17 00:00:00 2001
From: Daniel Huigens <d.huigens@protonmail.com>
Date: Wed, 11 Jul 2018 18:56:45 +0200
Subject: [PATCH 5/5] Various key revocation fixes

---
 src/encoding/armor.js   |  23 +++++---
 src/key.js              | 118 +++++++++++++++++-----------------------
 src/message.js          |  14 ++++-
 src/openpgp.js          |  17 +++---
 test/general/key.js     |  59 ++++++++++----------
 test/general/openpgp.js |  23 ++++----
 6 files changed, 121 insertions(+), 133 deletions(-)

diff --git a/src/encoding/armor.js b/src/encoding/armor.js
index 24298528..c18d818c 100644
--- a/src/encoding/armor.js
+++ b/src/encoding/armor.js
@@ -95,9 +95,10 @@ function getType(text) {
  * packet block.
  * @author  Alex
  * @version 2011-12-16
+ * @param {String} customComment (optional) additional comment to add to the armored string
  * @returns {String} The header information
  */
-function addheader() {
+function addheader(customComment) {
   let result = "";
   if (config.show_version) {
     result += "Version: " + config.versionstring + '\r\n';
@@ -105,6 +106,9 @@ function addheader() {
   if (config.show_comment) {
     result += "Comment: " + config.commentstring + '\r\n';
   }
+  if (customComment) {
+    result += "Comment: " + customComment + '\r\n';
+  }
   result += '\r\n';
   return result;
 }
@@ -326,22 +330,23 @@ function dearmor(text) {
  * @param body
  * @param {Integer} partindex
  * @param {Integer} parttotal
+ * @param {String} customComment (optional) additional comment to add to the armored string
  * @returns {String} Armored text
  * @static
  */
-function armor(messagetype, body, partindex, parttotal) {
+function armor(messagetype, body, partindex, parttotal, customComment) {
   const result = [];
   switch (messagetype) {
     case enums.armor.multipart_section:
       result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
-      result.push(addheader());
+      result.push(addheader(customComment));
       result.push(base64.encode(body));
       result.push("\r\n=" + getCheckSum(body) + "\r\n");
       result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
       break;
     case enums.armor.multipart_last:
       result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n");
-      result.push(addheader());
+      result.push(addheader(customComment));
       result.push(base64.encode(body));
       result.push("\r\n=" + getCheckSum(body) + "\r\n");
       result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n");
@@ -351,35 +356,35 @@ function armor(messagetype, body, partindex, parttotal) {
       result.push("Hash: " + body.hash + "\r\n\r\n");
       result.push(body.text.replace(/^-/mg, "- -"));
       result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n");
-      result.push(addheader());
+      result.push(addheader(customComment));
       result.push(base64.encode(body.data));
       result.push("\r\n=" + getCheckSum(body.data) + "\r\n");
       result.push("-----END PGP SIGNATURE-----\r\n");
       break;
     case enums.armor.message:
       result.push("-----BEGIN PGP MESSAGE-----\r\n");
-      result.push(addheader());
+      result.push(addheader(customComment));
       result.push(base64.encode(body));
       result.push("\r\n=" + getCheckSum(body) + "\r\n");
       result.push("-----END PGP MESSAGE-----\r\n");
       break;
     case enums.armor.public_key:
       result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n");
-      result.push(addheader());
+      result.push(addheader(customComment));
       result.push(base64.encode(body));
       result.push("\r\n=" + getCheckSum(body) + "\r\n");
       result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n");
       break;
     case enums.armor.private_key:
       result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
-      result.push(addheader());
+      result.push(addheader(customComment));
       result.push(base64.encode(body));
       result.push("\r\n=" + getCheckSum(body) + "\r\n");
       result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n");
       break;
     case enums.armor.signature:
       result.push("-----BEGIN PGP SIGNATURE-----\r\n");
-      result.push(addheader());
+      result.push(addheader(customComment));
       result.push(base64.encode(body));
       result.push("\r\n=" + getCheckSum(body) + "\r\n");
       result.push("-----END PGP SIGNATURE-----\r\n");
diff --git a/src/key.js b/src/key.js
index 2649f659..f2be5e9d 100644
--- a/src/key.js
+++ b/src/key.js
@@ -626,27 +626,26 @@ async function mergeSignatures(source, dest, attr, checkFn) {
 
 /**
  * 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, {
+Key.prototype.revoke = async function({
   flag: reasonForRevocationFlag=enums.reasonForRevocation.no_reason,
   string: reasonForRevocationString=''
 } = {}, date=new Date()) {
-  if (privateKey.primaryKey.getFingerprint() !== this.primaryKey.getFingerprint()) {
-    throw new Error('Private key does not match public key');
+  if (this.isPublic()) {
+    throw new Error('Need private key for revoking');
   }
   const dataToSign = { key: this.primaryKey };
   const key = new Key(this.toPacketlist());
-  key.revocationSignature = await createSignaturePacket(dataToSign, privateKey, {
+  key.revocationSignatures.push(await createSignaturePacket(dataToSign, null, this.primaryKey, {
     signatureType: enums.signature.key_revocation,
     reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
     reasonForRevocationString
-  }, date);
+  }, date));
   return key;
 };
 
@@ -656,30 +655,22 @@ Key.prototype.revoke = async function(privateKey, {
  * @return {String} armored revocation certificate
  */
 Key.prototype.getRevocationCertificate = function() {
-  if (this.revocationSignature) {
-    const commentstring = config.commentstring;
-    config.commentstring = 'This is a revocation certificate';
-    try {
-      const packetlist = new packet.List();
-      packetlist.push(this.revocationSignature);
-      return armor.encode(enums.armor.public_key, packetlist.write());
-    } finally {
-      // Restore comment string. armor.encode() shouldn't throw, but just to be sure it's wrapped in a try/finally
-      config.commentstring = commentstring;
-    }
+  if (this.revocationSignatures.length) {
+    const packetlist = new packet.List();
+    packetlist.push(getLatestSignature(this.revocationSignatures));
+    return armor.encode(enums.armor.public_key, packetlist.write(), null, null, 'This is a revocation certificate');
   }
 };
 
 /**
  * Applies a revocation certificate to a key
+ * This adds the first signature packet in the armored text to the key,
+ * if it is a valid revocation signature.
  * @param  {String} revocationCertificate armored revocation certificate
  * @return {module:key~Key} new revoked key
  */
 Key.prototype.applyRevocationCertificate = async function(revocationCertificate) {
   const input = armor.decode(revocationCertificate);
-  if (input.type !== enums.armor.public_key) {
-    throw new Error('Armored text not of type public key');
-  }
   const packetlist = new packet.List();
   packetlist.read(input.data);
   const revocationSignature = packetlist.findPacket(enums.packet.signature);
@@ -696,7 +687,7 @@ Key.prototype.applyRevocationCertificate = async function(revocationCertificate)
     throw new Error('Could not verify revocation signature');
   }
   const key = new Key(this.toPacketlist());
-  key.revocationSignature = revocationSignature;
+  key.revocationSignatures.push(revocationSignature);
   return key;
 };
 
@@ -818,11 +809,22 @@ User.prototype.toPacketlist = function() {
 User.prototype.sign = async function(primaryKey, privateKeys) {
   const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey };
   const user = new User(dataToSign.userid);
-  user.otherCertifications = await Promise.all(privateKeys.map(function(privateKey) {
+  user.otherCertifications = await Promise.all(privateKeys.map(async function(privateKey) {
+    if (privateKey.isPublic()) {
+      throw new Error('Need private key for signing');
+    }
     if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) {
       throw new Error('Not implemented for self signing');
     }
-    return createSignaturePacket(dataToSign, privateKey, {
+    const signingKeyPacket = await 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.');
+    }
+    return createSignaturePacket(dataToSign, privateKey, signingKeyPacket, {
       // Most OpenPGP implementations use generic certification (0x10)
       signatureType: enums.signature.cert_generic,
       keyFlags: [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]
@@ -857,31 +859,21 @@ User.prototype.isRevoked = async function(primaryKey, certificate, key, date=new
 /**
  * Create signature packet
  * @param  {Object}                          dataToSign Contains packets to be signed
- * @param  {module:key~Key}                  privateKey private key with decrypted secret key data for signing
+ * @param  {module:packet.SecretKey|
+ *          module:packet.SecretSubkey}      signingKeyPacket secret key packet for signing
  * @param  {Object} signatureProperties      (optional) properties to write on the signature packet before signing
  * @param  {Date} date                       (optional) override the creationtime of the signature
  * @param  {Object} userId                   (optional) user ID
  * @return {module:packet/signature}         signature packet
  */
-export async function createSignaturePacket(dataToSign, privateKey, signatureProperties, date, userId) {
-  if (privateKey.isPublic()) {
-    throw new Error('Need private key for signing');
-  }
-  await privateKey.verifyPrimaryUser();
-  const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId);
-  if (!signingKeyPacket) {
-    throw new Error(`Could not find valid signing key packet in key ${
-        privateKey.primaryKey.getKeyId().toHex()}`);
-  }
+export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userId) {
   if (!signingKeyPacket.isDecrypted) {
     throw new Error('Private key is not decrypted.');
   }
   const signaturePacket = new packet.Signature(date);
-  for(const [prop, value] of Object.entries(signatureProperties)) {
-    signaturePacket[prop] = value;
-  }
+  Object.assign(signaturePacket, signatureProperties);
   signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
-  signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId);
+  signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userId);
   await signaturePacket.sign(signingKeyPacket, dataToSign);
   return signaturePacket;
 }
@@ -1125,28 +1117,24 @@ SubKey.prototype.update = async function(subKey, primaryKey) {
 
 /**
  * Revokes the subkey
- * @param  {module:packet/signature} primaryKey primary key used for revocation
- * @param  {module:key~Key} privateKey decrypted private key for revocation
+ * @param  {module:packet.SecretKey} primaryKey decrypted private primary 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~SubKey} new subkey with revocation signature
  */
-SubKey.prototype.revoke = async function(primaryKey, privateKey, {
+SubKey.prototype.revoke = async function(primaryKey, {
   flag: reasonForRevocationFlag=enums.reasonForRevocation.no_reason,
   string: reasonForRevocationString=''
 } = {}, date=new Date()) {
-  if (privateKey.primaryKey.getFingerprint() !== primaryKey.getFingerprint()) {
-    throw new Error('Private key does not match public key');
-  }
   const dataToSign = { key: primaryKey, bind: this.subKey };
   const subKey = new SubKey(this.subKey);
-  subKey.revocationSignature = await createSignaturePacket(dataToSign, privateKey, {
+  subKey.revocationSignatures.push(await createSignaturePacket(dataToSign, null, primaryKey, {
     signatureType: enums.signature.subkey_revocation,
     reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag),
     reasonForRevocationString
-  }, date);
+  }, date));
   await subKey.update(this, primaryKey);
   return subKey;
 };
@@ -1223,7 +1211,6 @@ export function readArmored(armoredText) {
  * @param  {Date} date         Override the creation date of the key and the key signatures
  * @param  {Array<Object>} subkeys   (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
  *                                              sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
- * @param {Boolean} [options.revoked=false] Whether the key should include a revocation signature
  * @returns {Promise<module:key.Key>}
  * @async
  * @static
@@ -1384,7 +1371,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) {
     const signaturePacket = new packet.Signature(options.date);
     signaturePacket.signatureType = enums.signature.cert_generic;
     signaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm;
-    signaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretKeyPacket);
+    signaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, 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)
@@ -1440,7 +1427,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) {
     const subkeySignaturePacket = new packet.Signature(subkeyOptions.date);
     subkeySignaturePacket.signatureType = enums.signature.subkey_binding;
     subkeySignaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm;
-    subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(secretSubkeyPacket);
+    subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, secretSubkeyPacket);
     subkeySignaturePacket.keyFlags = subkeyOptions.sign ? enums.keyFlags.sign_data : [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage];
     if (subkeyOptions.keyExpirationTime > 0) {
       subkeySignaturePacket.keyExpirationTime = subkeyOptions.keyExpirationTime;
@@ -1456,18 +1443,14 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) {
     });
   });
 
-  if (options.revoked) {
-    const dataToSign = {};
-    dataToSign.key = secretKeyPacket;
-    const revocationSignaturePacket = new packet.Signature();
-    revocationSignaturePacket.signatureType = enums.signature.key_revocation;
-    revocationSignaturePacket.reasonForRevocationFlag = enums.reasonForRevocation.no_reason;
-    revocationSignaturePacket.reasonForRevocationString = '';
-    revocationSignaturePacket.publicKeyAlgorithm = options.keyType;
-    revocationSignaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket);
-    await revocationSignaturePacket.sign(secretKeyPacket, dataToSign);
-    packetlist.push(revocationSignaturePacket);
-  }
+  // Add revocation signature packet for creating a revocation certificate.
+  // This packet should be removed before returning the key.
+  const dataToSign = { key: secretKeyPacket };
+  packetlist.push(await createSignaturePacket(dataToSign, null, secretKeyPacket, {
+    signatureType: enums.signature.key_revocation,
+    reasonForRevocationFlag: enums.reasonForRevocation.no_reason,
+    reasonForRevocationString: ''
+  }, options.date));
 
   // set passphrase protection
   if (options.passphrase) {
@@ -1546,13 +1529,14 @@ function getExpirationTime(keyPacket, signature) {
 
 /**
  * Returns the preferred signature hash algorithm of a key
- * @param  {object} key
+ * @param  {module:key.Key} key (optional) the key to get preferences from
+ * @param  {module:packet.SecretKey|module:packet.SecretSubkey} keyPacket key packet used for signing
  * @param  {Date} date (optional) use the given date for verification instead of the current time
  * @param  {Object} userId (optional) user ID
  * @returns {Promise<String>}
  * @async
  */
-export async function getPreferredHashAlgo(key, date=new Date(), userId={}) {
+export async function getPreferredHashAlgo(key, keyPacket, date=new Date(), userId={}) {
   let hash_algo = config.prefer_hash_algorithm;
   let pref_algo = hash_algo;
   if (key instanceof Key) {
@@ -1562,19 +1546,17 @@ export async function getPreferredHashAlgo(key, date=new Date(), userId={}) {
       hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ?
         pref_algo : hash_algo;
     }
-    // disable expiration checks
-    key = key.getSigningKeyPacket(undefined, null, userId);
   }
-  switch (Object.getPrototypeOf(key)) {
+  switch (Object.getPrototypeOf(keyPacket)) {
     case packet.SecretKey.prototype:
     case packet.PublicKey.prototype:
     case packet.SecretSubkey.prototype:
     case packet.PublicSubkey.prototype:
-      switch (key.algorithm) {
+      switch (keyPacket.algorithm) {
         case 'ecdh':
         case 'ecdsa':
         case 'eddsa':
-          pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(key.params[0]);
+          pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.params[0]);
       }
   }
   return crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ?
diff --git a/src/message.js b/src/message.js
index ffba8a94..cdeb888f 100644
--- a/src/message.js
+++ b/src/message.js
@@ -428,7 +428,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new
     }
     const onePassSig = new packet.OnePassSignature();
     onePassSig.type = signatureType;
-    onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, date, userId);
+    onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userId);
     onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
     onePassSig.signingKeyId = signingKeyPacket.getKeyId();
     if (i === privateKeys.length - 1) {
@@ -499,8 +499,16 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
   const signatureType = literalDataPacket.text === null ?
     enums.signature.binary : enums.signature.text;
 
-  await Promise.all(privateKeys.map(privateKey => {
-    return createSignaturePacket(literalDataPacket, privateKey, {signatureType}, date, userId);
+  await Promise.all(privateKeys.map(async privateKey => {
+    if (privateKey.isPublic()) {
+      throw new Error('Need private key for signing');
+    }
+    const signingKeyPacket = await privateKey.getSigningKeyPacket(undefined, date, userId);
+    if (!signingKeyPacket) {
+      throw new Error(`Could not find valid signing key packet in key ${
+          privateKey.primaryKey.getKeyId().toHex()}`);
+    }
+    return createSignaturePacket(literalDataPacket, privateKey, signingKeyPacket, {signatureType}, date, userId);
   })).then(signatureList => {
     signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
   });
diff --git a/src/openpgp.js b/src/openpgp.js
index 37c279a1..9517837e 100644
--- a/src/openpgp.js
+++ b/src/openpgp.js
@@ -108,16 +108,15 @@ export function destroyWorker() {
  * @param  {Date} date               (optional) override the creation date of the key and the key signatures
  * @param  {Array<Object>} subkeys   (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
  *                                              sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
- * @param  {Boolean} revocationCertificate (optional) Whether the returned object should include a revocation certificate to revoke the public key
  * @returns {Promise<Object>}         The generated key object in the form:
  *                                     { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String }
  * @async
  * @static
  */
 
-export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}], revocationCertificate=true }) {
+export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpirationTime=0, curve="", date=new Date(), subkeys=[{}] }) {
   userIds = toArray(userIds);
-  const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys, revocationCertificate };
+  const options = { userIds, passphrase, numBits, keyExpirationTime, curve, date, subkeys };
   if (util.getWebCryptoAll() && numBits < 2048) {
     throw new Error('numBits should be 2048 or 4096, found: ' + numBits);
   }
@@ -126,11 +125,9 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
     return asyncProxy.delegate('generateKey', options);
   }
 
-  options.revoked = options.revocationCertificate;
-
   return generate(options).then(key => {
     const revocationCertificate = key.getRevocationCertificate();
-    key.revocationSignature = null;
+    key.revocationSignatures = [];
 
     return {
 
@@ -157,7 +154,7 @@ export function generateKey({ userIds=[], passphrase="", numBits=2048, keyExpira
  */
 export function reformatKey({privateKey, userIds=[], passphrase="", keyExpirationTime=0, date, revocationCertificate=true}) {
   userIds = toArray(userIds);
-  const options = { privateKey, userIds, passphrase, keyExpirationTime, date, revocationCertificate=true};
+  const options = { privateKey, userIds, passphrase, keyExpirationTime, date, revocationCertificate };
   if (asyncProxy) {
     return asyncProxy.delegate('reformatKey', options);
   }
@@ -166,7 +163,7 @@ export function reformatKey({privateKey, userIds=[], passphrase="", keyExpiratio
 
   return reformat(options).then(key => {
     const revocationCertificate = key.getRevocationCertificate();
-    key.revocationSignature = null;
+    key.revocationSignatures = [];
 
     return {
 
@@ -207,10 +204,10 @@ export function revokeKey({
     if (revocationCertificate) {
       return key.applyRevocationCertificate(revocationCertificate);
     } else {
-      return key.revoke(key, reasonForRevocation);
+      return key.revoke(reasonForRevocation);
     }
   }).then(key => {
-    if(key.isPrivate()) {
+    if (key.isPrivate()) {
       const publicKey = key.toPublic();
       return {
         privateKey: key,
diff --git a/test/general/key.js b/test/general/key.js
index 4d6f319f..e208a2d7 100644
--- a/test/general/key.js
+++ b/test/general/key.js
@@ -114,7 +114,7 @@ function tests() {
       'hz3tYjKhoFTKEIq3y3Pp',
       '=h/aX',
       '-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
-      
+
   const pub_key_arm4 = `-----BEGIN PGP PUBLIC KEY BLOCK-----
 Version: GnuPG 2.1.15 (GNU/Linux)
 
@@ -1329,7 +1329,7 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
   });
 
   it('Verify status of revoked primary key', function(done) {
-    const pubKey = openpgp.key.readArmored(pub_revoked).keys[0];
+    const pubKey = openpgp.key.readArmored(pub_revoked_subkeys).keys[0];
     expect(pubKey.verifyPrimaryKey()).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done);
   });
 
@@ -1520,41 +1520,40 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
     });
   });
 
-  it('revoke() - primary key', function(done) {
-    const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
+  it('revoke() - primary key', async function() {
     const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0];
-    privKey.decrypt('hello world');
+    await privKey.decrypt('hello world');
 
-    pubKey.revoke(privKey, {
+    await privKey.revoke({
       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');
+    }).then(async revKey => {
+      expect(revKey.revocationSignatures).to.exist.and.have.length(1);
+      expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.key_revocation);
+      expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_retired);
+      expect(revKey.revocationSignatures[0].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);
+      expect(await privKey.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.valid);
+      expect(await revKey.verifyPrimaryKey()).to.equal(openpgp.enums.keyStatus.revoked);
     });
   });
 
-  it('revoke() - subkey', function(done) {
+  it('revoke() - subkey', async function() {
     const pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0];
     const privKey = openpgp.key.readArmored(priv_key_arm2).keys[0];
-    privKey.decrypt('hello world');
+    await privKey.decrypt('hello world');
 
     const subKey = pubKey.subKeys[0];
-    subKey.revoke(pubKey.primaryKey, privKey, {
+    await subKey.revoke(privKey.primaryKey, {
       flag: openpgp.enums.reasonForRevocation.key_superseded
-    }).then(revKey => {
-      expect(revKey.revocationSignature).to.exist;
-      expect(revKey.revocationSignature.signatureType).to.equal(openpgp.enums.signature.subkey_revocation);
-      expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_superseded);
-      expect(revKey.revocationSignature.reasonForRevocationString).to.equal('');
+    }).then(async revKey => {
+      expect(revKey.revocationSignatures).to.exist.and.have.length(1);
+      expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.subkey_revocation);
+      expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_superseded);
+      expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('');
 
-      expect(subKey.verify(pubKey.primaryKey)).to.eventually.equal(openpgp.enums.keyStatus.valid);
-      expect(revKey.verify(pubKey.primaryKey)).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done);
+      expect(await subKey.verify(pubKey.primaryKey)).to.equal(openpgp.enums.keyStatus.valid);
+      expect(await revKey.verify(pubKey.primaryKey)).to.equal(openpgp.enums.keyStatus.revoked);
     });
   });
 
@@ -1575,7 +1574,7 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
     packetlist.read(input.data);
     const armored = openpgp.armor.encode(openpgp.enums.armor.public_key, packetlist.write());
 
-    expect(revocationCertificate.replace(/^Comment: .*$/m, '')).to.equal(armored.replace(/^Comment: .*$/m, ''));
+    expect(revocationCertificate.replace(/^Comment: .*$\r\n/mg, '')).to.equal(armored.replace(/^Comment: .*$\r\n/mg, ''));
   });
 
   it('getRevocationCertificate() should have an appropriate comment', function() {
@@ -2137,8 +2136,8 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
     return openpgp.generateKey(opt).then(function(original) {
       return openpgp.revokeKey({key: original.key.toPublic(), revocationCertificate: original.revocationCertificate}).then(function(revKey) {
         revKey = revKey.publicKey;
-        expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason);
-        expect(revKey.revocationSignature.reasonForRevocationString).to.equal('');
+        expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason);
+        expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('');
         return revKey.verifyPrimaryKey().then(function(status) {
           expect(status).to.equal(openpgp.enums.keyStatus.revoked);
         });
@@ -2149,12 +2148,12 @@ const mergeKey2 = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
   it('Revoke generated key with private key', function() {
     const opt = {numBits: 512, userIds: 'test1 <a@b.com>', passphrase: '1234'};
     if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
-    return openpgp.generateKey(opt).then(function(original) {
-      original.key.decrypt('1234');
+    return openpgp.generateKey(opt).then(async function(original) {
+      await original.key.decrypt('1234');
       return openpgp.revokeKey({key: original.key, reasonForRevocation: {string: 'Testing key revocation'}}).then(function(revKey) {
         revKey = revKey.publicKey;
-        expect(revKey.revocationSignature.reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason);
-        expect(revKey.revocationSignature.reasonForRevocationString).to.equal('Testing key revocation');
+        expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason);
+        expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('Testing key revocation');
         return revKey.verifyPrimaryKey().then(function(status) {
           expect(status).to.equal(openpgp.enums.keyStatus.revoked);
         });
diff --git a/test/general/openpgp.js b/test/general/openpgp.js
index 4b9f9aac..536a12be 100644
--- a/test/general/openpgp.js
+++ b/test/general/openpgp.js
@@ -485,10 +485,7 @@ describe('OpenPGP.js public api tests', function() {
             }
           };
         },
-        getRevocationCertificate: function() {},
-        removeRevocationCertificate: function() {
-          return this;
-        }
+        getRevocationCertificate: function() {}
       };
       keyGenStub = stub(openpgp.key, 'generate');
       keyGenStub.returns(resolves(keyObjStub));
@@ -517,9 +514,7 @@ describe('OpenPGP.js public api tests', function() {
           keyExpirationTime: 0,
           curve: "",
           date: now,
-          subkeys: [],
-          revocationCertificate: true,
-          revoked: true,
+          subkeys: []
         }).calledOnce).to.be.true;
         expect(newKey.key).to.exist;
         expect(newKey.privateKeyArmored).to.exist;
@@ -1862,7 +1857,7 @@ describe('OpenPGP.js public api tests', function() {
             });
         });
 
-        it.skip('should fail to encrypt with revoked key', function() {
+        it('should fail to encrypt with revoked key', function() {
           return openpgp.revokeKey({
             key: privateKey.keys[0]
           }).then(function(revKey) {
@@ -1877,13 +1872,15 @@ describe('OpenPGP.js public api tests', function() {
           });
         });
 
-        it.skip('should fail to encrypt with revoked subkey', function() {
-          let clonedKey = privateKey.keys[0].toPublic();
-          return clonedKey.subKeys[0].revoke(clonedKey.primaryKey, privateKey.keys[0]).then(function(revSubKey) {
-            clonedKey.subKeys[0] = revSubKey;
+        it('should fail to encrypt with revoked subkey', async function() {
+          const pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0];
+          const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0];
+          await privKeyDE.decrypt(passphrase);
+          return privKeyDE.subKeys[0].revoke(privKeyDE.primaryKey).then(function(revSubKey) {
+            pubKeyDE.subKeys[0] = revSubKey;
             return openpgp.encrypt({
               data: plaintext,
-              publicKeys: clonedKey
+              publicKeys: pubKeyDE
             }).then(function(encrypted) {
               throw new Error('Should not encrypt with revoked subkey');
             }).catch(function(error) {