From a1c47ecdea790e1a8c3aca8e3868c5aa00811d05 Mon Sep 17 00:00:00 2001
From: Daniel Huigens <d.huigens@protonmail.com>
Date: Thu, 20 Sep 2018 15:28:03 +0200
Subject: [PATCH] Indicate an error when parsing a key with an authorized
 revocation key

Since we will ignore revocation signatures from authorized revocation keys,
it is dangerous to use these keys.
---
 src/key.js               |  16 +++++--
 src/packet/packetlist.js | 100 ++-------------------------------------
 test/general/key.js      |  44 +++++++++++++++++
 3 files changed, 59 insertions(+), 101 deletions(-)

diff --git a/src/key.js b/src/key.js
index ac6d2f4f..c96138db 100644
--- a/src/key.js
+++ b/src/key.js
@@ -1220,9 +1220,16 @@ SubKey.prototype.revoke = async function(primaryKey, {
 export async function read(data) {
   const result = {};
   result.keys = [];
+  const err = [];
   try {
     const packetlist = new packet.List();
     await packetlist.read(data);
+    if (packetlist.filterByTag(enums.packet.signature).some(
+      signature => signature.revocationKeyClass !== null
+    )) {
+      // Indicate an error, but still parse the key.
+      err.push(new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'));
+    }
     const keyIndex = packetlist.indexOfTag(enums.packet.publicKey, enums.packet.secretKey);
     if (keyIndex.length === 0) {
       throw new Error('No key packet found');
@@ -1233,13 +1240,14 @@ export async function read(data) {
         const newKey = new Key(oneKeyList);
         result.keys.push(newKey);
       } catch (e) {
-        result.err = result.err || [];
-        result.err.push(e);
+        err.push(e);
       }
     }
   } catch (e) {
-    result.err = result.err || [];
-    result.err.push(e);
+    err.push(e);
+  }
+  if (err.length) {
+    result.err = err;
   }
   return result;
 }
diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js
index 12b09754..f1394017 100644
--- a/src/packet/packetlist.js
+++ b/src/packet/packetlist.js
@@ -21,6 +21,7 @@ import util from '../util';
  * are stored as numerical indices.
  * @memberof module:packet
  * @constructor
+ * @extends Array
  */
 function List() {
   /**
@@ -31,6 +32,8 @@ function List() {
   this.length = 0;
 }
 
+List.prototype = [];
+
 /**
  * Reads a stream of binary data and interprents it as a list of packets.
  * @param {Uint8Array | ReadableStream<Uint8Array>} A Uint8Array of bytes.
@@ -145,37 +148,6 @@ List.prototype.push = function (packet) {
   this.length++;
 };
 
-/**
- * Remove a packet from the list and return it.
- * @returns {Object}   The packet that was removed
- */
-List.prototype.pop = function() {
-  if (this.length === 0) {
-    return;
-  }
-
-  const packet = this[this.length - 1];
-  delete this[this.length - 1];
-  this.length--;
-
-  return packet;
-};
-
-/**
- * Creates a new PacketList with all packets that pass the test implemented by the provided function.
- */
-List.prototype.filter = function (callback) {
-  const filtered = new List();
-
-  for (let i = 0; i < this.length; i++) {
-    if (callback(this[i], i, this)) {
-      filtered.push(this[i]);
-    }
-  }
-
-  return filtered;
-};
-
 /**
  * Creates a new PacketList with all packets from the given types
  */
@@ -193,58 +165,6 @@ List.prototype.filterByTag = function (...args) {
   return filtered;
 };
 
-/**
- * Executes the provided callback once for each element
- */
-List.prototype.forEach = function (callback) {
-  for (let i = 0; i < this.length; i++) {
-    callback(this[i], i, this);
-  }
-};
-
-/**
- * Returns an array containing return values of callback
- * on each element
- */
-List.prototype.map = function (callback) {
-  const packetArray = [];
-
-  for (let i = 0; i < this.length; i++) {
-    packetArray.push(callback(this[i], i, this));
-  }
-
-  return packetArray;
-};
-
-/**
- * Executes the callback function once for each element
- * until it finds one where callback returns a truthy value
- * @param  {Function} callback
- * @returns {Promise<Boolean>}
- * @async
- */
-List.prototype.some = async function (callback) {
-  for (let i = 0; i < this.length; i++) {
-    if (await callback(this[i], i, this)) {
-      return true;
-    }
-  }
-  return false;
-};
-
-/**
- * Executes the callback function once for each element,
- * returns true if all callbacks returns a truthy value
- */
-List.prototype.every = function (callback) {
-  for (let i = 0; i < this.length; i++) {
-    if (!callback(this[i], i, this)) {
-      return false;
-    }
-  }
-  return true;
-};
-
 /**
  * Traverses packet tree and returns first matching packet
  * @param  {module:enums.packet} type The packet type
@@ -285,20 +205,6 @@ List.prototype.indexOfTag = function (...args) {
   return tagIndex;
 };
 
-/**
- * Returns slice of packetlist
- */
-List.prototype.slice = function (begin, end) {
-  if (!end) {
-    end = this.length;
-  }
-  const part = new List();
-  for (let i = begin; i < end; i++) {
-    part.push(this[i]);
-  }
-  return part;
-};
-
 /**
  * Concatenates packetlist or array of packets
  */
diff --git a/test/general/key.js b/test/general/key.js
index 9de32570..cd37ed34 100644
--- a/test/general/key.js
+++ b/test/general/key.js
@@ -1223,6 +1223,41 @@ t/ia1kMpSEiOVLlX5dfHZzhR3WNtBqU=
 =C0fJ
 -----END PGP PRIVATE KEY BLOCK-----`;
 
+const key_with_authorized_revocation_key = `-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: OpenPGP.js VERSION
+Comment: https://openpgpjs.org
+
+xsBNBFujnwwBCADK1xX03tSCmktPDS9Ncij3O5wG+F5/5Zm7QJDc39Wt1t/K
+szCSobWtm/UObQVZjsTGwg0ZUPgepgWGGDBL0dlc1NObwUiOhGYnJnd4V25P
+iU5Mg3+DhRq+LzNK+oGlpVPDpwQ48S8HOphbswKUpuaDcEQ2f+NKIc0eXe5m
+ut5x9uoVj8jneUNsHYq6FIlxh4knzpJWFj5+LNi7plMCwKip6srVNf8He/q0
+0xA/4vjSIOfGIE7TCBH33CbHEr98p81Cf4g0E+kswEz5iWE2SDCyYgQkMrkz
+H9mtVqk3nFT8NR0USxKqH9bGhaTx1AWq/HDgsphayPEWQ0usjQDrbQUnABEB
+AAHNDnRlc3QgPGFAYi5jb20+wsCNBBABCABBBQJbo58MBgsJBwgDAhcMgAH0
+cOUNyxrV8eZOCGRKY2E6TW5AlAkQWICi/ReDcrkEFQgKAgMWAgECGQECGwMC
+HgEAADgHB/0WIHh6maX2LZ0u5ujk1tZxWMrCycccopdQNKN0RGD98X4fyY6J
+wfmKb107gcidJBFct0sVWFW8GU42w9pVMU5qWD6kyFkgcmov319UL+7aZ19b
+HOWVKUTb6rFG8/qAbq3BF7YB/cZIBWMFKAS3BRJ4Kz23GheAB2A9oVLmuq5o
+gW5c2R1YC0T0XyXEFiw9uZ+AS6kEZymFPRQfPUIbJs1ct/lAN+mC9Qp0Y6CL
+60Hd6jlKUb6TgljaQ6CtLfT9v72GeKznapKr9IEtsgYv69j0c/MRM2nmu50c
+g+fICiiHrTbNS6jkUz0pZLe7hdhWHeEiqcA9+GC1DxOQCRCS/YNfzsBNBFuj
+nwwBCADK1xX03tSCmktPDS9Ncij3O5wG+F5/5Zm7QJDc39Wt1t/KszCSobWt
+m/UObQVZjsTGwg0ZUPgepgWGGDBL0dlc1NObwUiOhGYnJnd4V25PiU5Mg3+D
+hRq+LzNK+oGlpVPDpwQ48S8HOphbswKUpuaDcEQ2f+NKIc0eXe5mut5x9uoV
+j8jneUNsHYq6FIlxh4knzpJWFj5+LNi7plMCwKip6srVNf8He/q00xA/4vjS
+IOfGIE7TCBH33CbHEr98p81Cf4g0E+kswEz5iWE2SDCyYgQkMrkzH9mtVqk3
+nFT8NR0USxKqH9bGhaTx1AWq/HDgsphayPEWQ0usjQDrbQUnABEBAAHCwF8E
+GAEIABMFAlujnwwJEFiAov0Xg3K5AhsMAACI/QgArvTcutod+7n1D8wCwM50
+jo3x4KPuQw+NwbOnMbFwv0R8i8NqtSFf2bYkkZ7RLViTmphvSon4h2WgfczL
+SBulZ1QZF7zCKXmXDg8/HZgRUflC1XMixpB8Hqouin5AVgMbsbHg30V2uPco
+V3DeFQ8HWxQC9symaMW/20MkqNXgCjM0us7kVwTEJQqZ6KYrFVjKyprSQRyP
+rfckEBuZnj91OS+kAHlZ+ScZIuV4QVF0e2U6oEuB+qFXppR030PJoO6WDOzm
+67hkzfrc3VpKw/I+vVJnZb4GOhNTSpa+p8i4gTyWmrOZ0G85QoldHWlWaRBg
+lbjwPj3QUTbLFvHisYzXEQ==
+=aT8U
+-----END PGP PUBLIC KEY BLOCK-----
+`;
+
 function versionSpecificTests() {
   it('Preferences of generated key', function() {
     const testPref = function(key) {
@@ -1688,6 +1723,15 @@ describe('Key', function() {
     expect(pubKeys.keys[1].getKeyId().toHex()).to.equal('dbf223e870534df4');
   });
 
+  it('Parsing armored key with an authorized revocation key', async function() {
+    const pubKeys = await openpgp.key.readArmored(key_with_authorized_revocation_key);
+    expect(pubKeys).to.exist;
+    expect(pubKeys.err).to.exist.and.have.length(1);
+    expect(pubKeys.err[0].message).to.equal('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.');
+    expect(pubKeys.keys).to.have.length(1);
+    expect(pubKeys.keys[0].getKeyId().toHex()).to.equal('5880a2fd178372b9');
+  });
+
   it('Parsing V5 public key packet', async function() {
     // Manually modified from https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b2092/back.mkd#sample-eddsa-key
     let packetBytes = openpgp.util.hex_to_Uint8Array(`