From de3ba184007868934f6f6a1b8336f2cb118a946b Mon Sep 17 00:00:00 2001
From: Tankred Hase <tankred@whiteout.io>
Date: Thu, 3 Jul 2014 14:57:52 +0200
Subject: [PATCH] Implement content verification using detached signatures

---
 package.json              |  2 +-
 src/encoding/armor.js     |  2 +-
 src/message.js            | 17 +++++++++++++++
 test/general/armor.js     |  4 ++--
 test/general/signature.js | 46 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 67 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 17683417..059f0e72 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "openpgp",
   "description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.",
-  "version": "0.7.0",
+  "version": "0.7.1-dev",
   "homepage": "http://openpgpjs.org/",
   "engines": {
     "node": ">=0.8"
diff --git a/src/encoding/armor.js b/src/encoding/armor.js
index 13ea2d0a..57708bf0 100644
--- a/src/encoding/armor.js
+++ b/src/encoding/armor.js
@@ -38,7 +38,7 @@ var base64 = require('./base64.js'),
  *         5 = PRIVATE KEY BLOCK
  */
 function getType(text) {
-  var reHeader = /^-----BEGIN PGP (MESSAGE, PART \d+\/\d+|MESSAGE, PART \d+|SIGNED MESSAGE|MESSAGE|PUBLIC KEY BLOCK|PRIVATE KEY BLOCK)-----$\n/m;
+  var reHeader = /^-----BEGIN PGP (MESSAGE, PART \d+\/\d+|MESSAGE, PART \d+|SIGNED MESSAGE|MESSAGE|PUBLIC KEY BLOCK|PRIVATE KEY BLOCK|SIGNATURE)-----$\n/m;
 
   var header = text.match(reHeader);
 
diff --git a/src/message.js b/src/message.js
index 8e758bdf..374f727f 100644
--- a/src/message.js
+++ b/src/message.js
@@ -290,6 +290,22 @@ function readArmored(armoredText) {
   return newMessage;
 }
 
+/**
+ * Create a message object from signed content and a detached armored signature.
+ * @param {String} content An 8 bit ascii string containing e.g. a MIME subtree with text nodes or attachments
+ * @param {String} detachedSignature The detached ascii armored PGP signarure
+ */
+function readSignedContent(content, detachedSignature) {
+  var literalDataPacket = new packet.Literal();
+  literalDataPacket.setBytes(content, enums.read(enums.literal, enums.literal.binary));
+  var packetlist = new packet.List();
+  packetlist.push(literalDataPacket);
+  var input = armor.decode(detachedSignature).data;
+  packetlist.read(input);
+  var newMessage = new Message(packetlist);
+  return newMessage;
+}
+
 /**
  * creates new message object from text
  * @param {String} text
@@ -323,5 +339,6 @@ function fromBinary(bytes) {
 
 exports.Message = Message;
 exports.readArmored = readArmored;
+exports.readSignedContent = readSignedContent;
 exports.fromText = fromText;
 exports.fromBinary = fromBinary;
diff --git a/test/general/armor.js b/test/general/armor.js
index 205766eb..a0a654e3 100644
--- a/test/general/armor.js
+++ b/test/general/armor.js
@@ -117,7 +117,7 @@ describe("ASCII armor", function() {
       ['-----BEGIN PGP SIGNED MESSAGE\u2010\u2010\u2010\u2010\u2010\nHash:SHA1\n\nIs this properly-----',
       '',
       'sign this',
-      '-----BEGIN PGP SIGNATURE-----',
+      '-----BEGIN PGP SIGNNATURE-----',
       'Version: GnuPG v2.0.22 (GNU/Linux)',
       '',
       'iJwEAQECAAYFAlMrPj0ACgkQ4IT3RGwgLJfYkQQAgHMQieazCVdfGAfzQM69Egm5',
@@ -125,7 +125,7 @@ describe("ASCII armor", function() {
       'LQ9DiQUwJMBE8nOwVR7Mpc4kLNngMTNaHAjZaVaDpTCrklPY+TPHIZnu0B6Ur+6t',
       'skTzzVXIxMYw8ihbHfk=',
       '=e/eA',
-      '-----END PGP SIGNATURE-----'].join('\n');
+      '-----END PGP SIGNNATURE-----'].join('\n');
 
     msg = openpgp.cleartext.readArmored.bind(null, msg);
     expect(msg).to.throw(Error, /Unknow ASCII armor type/);
diff --git a/test/general/signature.js b/test/general/signature.js
index 65a2c509..1da22c0e 100644
--- a/test/general/signature.js
+++ b/test/general/signature.js
@@ -590,4 +590,50 @@ describe("Signature", function() {
     expect(pubKey.users[0].selfCertifications).to.eql(pubKey2.users[0].selfCertifications);
   });
 
+  it('Verify a detached signature', function() {
+    var detachedSig = ['-----BEGIN PGP SIGNATURE-----',
+      'Version: GnuPG v1.4.13 (Darwin)',
+      'Comment: GPGTools - https://gpgtools.org',
+      'Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/',
+      '',
+      'iQEcBAEBCgAGBQJTqH5OAAoJENf7k/zfv8I8oFoH/R6EFTw2CYUQoOKSAQstWIHp',
+      'fVVseLOkFbByUV5eLuGVBNI3DM4GQ6C7dGntKAn34a1iTGcAIZH+fIhaZ2WtNdtA',
+      'R+Ijn8xDjbF/BWvcTBOaRvgw9b8viPxhkVYa3PioHYz6krt/LmFqFdp/phWZcqR4',
+      'jzWMX55h4FOw3YBNGiz2NuIg+iGrFRWPYgd8NVUmJKReZHs8C/6HGz7F4/A24k6Y',
+      '7xms9D6Er+MhspSl+1dlRdHjtXiRqC5Ld1hi2KBKc6YzgOLpVw5l9sffbnH+aRG4',
+      'dH+2J5U3elqBDK1i3GyG8ixLSB0FGW9+lhYNosZne2xy8SbQKdgsnTBnWSGevP0=',
+      '=xiih',
+      '-----END PGP SIGNATURE-----'
+    ].join('\r\n');
+
+    var content = ['Content-Type: multipart/mixed;',
+      ' boundary="------------070307080002050009010403"',
+      '',
+      'This is a multi-part message in MIME format.',
+      '--------------070307080002050009010403',
+      'Content-Type: text/plain; charset=ISO-8859-1',
+      'Content-Transfer-Encoding: quoted-printable',
+      '',
+      'test11',
+      '',
+      '--------------070307080002050009010403',
+      'Content-Type: application/macbinary;',
+      ' name="test.bin"',
+      'Content-Transfer-Encoding: base64',
+      'Content-Disposition: attachment;',
+      ' filename="test.bin"',
+      '',
+      'dGVzdGF0dGFjaG1lbnQ=',
+      '--------------070307080002050009010403--',
+      ''
+    ].join('\r\n');
+
+    var publicKeyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: OpenPGP.js v.1.20131116\r\nComment: Whiteout Mail - http://whiteout.io\r\n\r\nxsBNBFKODs4BB/9iOF4THsjQMY+WEpT7ShgKxj4bHzRRaQkqczS4nZvP0U3g\r\nqeqCnbpagyeKXA+bhWFQW4GmXtgAoeD5PXs6AZYrw3tWNxLKu2Oe6Tp9K/XI\r\nxTMQ2wl4qZKDXHvuPsJ7cmgaWqpPyXtxA4zHHS3WrkI/6VzHAcI/y6x4szSB\r\nKgSuhI3hjh3s7TybUC1U6AfoQGx/S7e3WwlCOrK8GTClirN/2mCPRC5wuIft\r\nnkoMfA6jK8d2OPrJ63shy5cgwHOjQg/xuk46dNS7tkvGmbaa+X0PgqSKB+Hf\r\nYPPNS/ylg911DH9qa8BqYU2QpNh9jUKXSF+HbaOM+plWkCSAL7czV+R3ABEB\r\nAAHNLVdoaXRlb3V0IFVzZXIgPHNhZmV3aXRobWUudGVzdHVzZXJAZ21haWwu\r\nY29tPsLAXAQQAQgAEAUCUo4O2gkQ1/uT/N+/wjwAAN2cB/9gFRmAfvEQ2qz+\r\nWubmT2EsSSnjPMxzG4uyykFoa+TaZCWo2Xa2tQghmU103kEkQb1OEjRjpgwJ\r\nYX9Kghnl8DByM686L5AXnRyHP78qRJCLXSXl0AGicboUDp5sovaa4rswQceH\r\nvcdWgZ/mgHTRoiQeJddy9k+H6MPFiyFaVcFwegVsmpc+dCcC8yT+qh8ZIbyG\r\nRJU60PmKKN7LUusP+8DbSv39zCGJCBlVVKyA4MzdF5uM+sqTdXbKzOrT5DGd\r\nCZaox4s+w16Sq1rHzZKFWfQPfKLDB9pyA0ufCVRA3AF6BUi7G3ZqhZiHNhMP\r\nNvE45V/hS1PbZcfPVoUjE2qc1Ix1\r\n=7Wpe\r\n-----END PGP PUBLIC KEY BLOCK-----';
+    var publicKeys = openpgp.key.readArmored(publicKeyArmored).keys;
+
+    var msg = openpgp.message.readSignedContent(content, detachedSig);
+    var result = msg.verify(publicKeys);
+    expect(result[0].valid).to.be.true;
+  });
+
 });