From 357d49f7e9fa3268bf39fdda5a45feba898dfd68 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= <toberndo@yarkon.de>
Date: Wed, 26 Mar 2014 18:04:58 +0100
Subject: [PATCH] OP-01-026 Errors in EMSA-PKCS1-v1_5 decoding routine (High)
 and OP-01-018 Suggested improvement in RSA signature verification (Low). Do
 RSA signature verification as described in RFC 3447 Section 8.2.2. Remove
 pkcs1.emsa.decode(). Rewrite pkcs1.emsa.encode(). Hash algorithms: throw
 exception on error condition.

---
 src/crypto/hash/ripe-md.js |  2 +-
 src/crypto/hash/sha.js     | 20 +++++-----
 src/crypto/pkcs1.js        | 77 +++++++++++++++++---------------------
 src/crypto/signature.js    | 17 +++------
 4 files changed, 52 insertions(+), 64 deletions(-)

diff --git a/src/crypto/hash/ripe-md.js b/src/crypto/hash/ripe-md.js
index 981d5a2c..c6275588 100644
--- a/src/crypto/hash/ripe-md.js
+++ b/src/crypto/hash/ripe-md.js
@@ -85,7 +85,7 @@ function mixOneRound(a, b, c, d, e, x, s, roundNumber) {
       break;
 
     default:
-      document.write("Bogus round number");
+      throw new Error("Bogus round number");
       break;
   }
 
diff --git a/src/crypto/hash/sha.js b/src/crypto/hash/sha.js
index 58c2ba13..14447f2c 100644
--- a/src/crypto/hash/sha.js
+++ b/src/crypto/hash/sha.js
@@ -87,7 +87,7 @@ var jsSHA = (function() {
         if (!isNaN(num)) {
           bin[i >> 3] |= num << (24 - (4 * (i % 8)));
         } else {
-          return "INVALID HEX STRING";
+          throw new Error("INVALID HEX STRING");
         }
       }
 
@@ -870,7 +870,7 @@ var jsSHA = (function() {
             H[7].highOrder, H[7].lowOrder];
         default:
           /* This should never be reached */
-          return [];
+          throw new Error('Unknown SHA variant');
       }
     },
 
@@ -896,7 +896,7 @@ var jsSHA = (function() {
       /* Convert the input string into the correct type */
       if ("HEX" === inputFormat) {
         if (0 !== (srcString.length % 2)) {
-          return "TEXT MUST BE IN BYTE INCREMENTS";
+          throw new Error("TEXT MUST BE IN BYTE INCREMENTS");
         }
         this.strBinLen = srcString.length * 4;
         this.strToHash = hex2binb(srcString);
@@ -905,7 +905,7 @@ var jsSHA = (function() {
         this.strBinLen = srcString.length * charSize;
         this.strToHash = str2binb(srcString);
       } else {
-        return "UNKNOWN TEXT INPUT TYPE";
+        throw new Error("UNKNOWN TEXT INPUT TYPE");
       }
     };
 
@@ -934,7 +934,7 @@ var jsSHA = (function() {
           formatFunc = binb2str;
           break;
         default:
-          return "FORMAT NOT RECOGNIZED";
+          throw new Error("FORMAT NOT RECOGNIZED");
       }
 
       switch (variant) {
@@ -964,7 +964,7 @@ var jsSHA = (function() {
           }
           return formatFunc(this.sha512);
         default:
-          return "HASH NOT RECOGNIZED";
+          throw new Error("HASH NOT RECOGNIZED");
       }
     },
 
@@ -998,7 +998,7 @@ var jsSHA = (function() {
           formatFunc = binb2str;
           break;
         default:
-          return "FORMAT NOT RECOGNIZED";
+          throw new Error("FORMAT NOT RECOGNIZED");
       }
 
       /* Validate the hash variant selection and set needed variables */
@@ -1024,14 +1024,14 @@ var jsSHA = (function() {
           hashBitSize = 512;
           break;
         default:
-          return "HASH NOT RECOGNIZED";
+          throw new Error("HASH NOT RECOGNIZED");
       }
 
       /* Validate input format selection */
       if ("HEX" === inputFormat) {
         /* Nibbles must come in pairs */
         if (0 !== (key.length % 2)) {
-          return "KEY MUST BE IN BYTE INCREMENTS";
+          throw new Error("KEY MUST BE IN BYTE INCREMENTS");
         }
         keyToUse = hex2binb(key);
         keyBinLen = key.length * 4;
@@ -1039,7 +1039,7 @@ var jsSHA = (function() {
         keyToUse = str2binb(key);
         keyBinLen = key.length * charSize;
       } else {
-        return "UNKNOWN KEY INPUT TYPE";
+        throw new Error("UNKNOWN KEY INPUT TYPE");
       }
 
       /* These are used multiple times, calculate and store them */
diff --git a/src/crypto/pkcs1.js b/src/crypto/pkcs1.js
index 88083a8c..2f6e4f85 100644
--- a/src/crypto/pkcs1.js
+++ b/src/crypto/pkcs1.js
@@ -91,7 +91,6 @@ module.exports = {
       result += message;
       return result;
     },
-
     /**
      * decodes a EME-PKCS1-v1_5 padding (See {@link http://tools.ietf.org/html/rfc4880#section-13.1.2|RFC 4880 13.1.2})
      * @param {String} message EME-PKCS1 padded message
@@ -110,53 +109,47 @@ module.exports = {
   },
 
   emsa: {
-
     /**
      * create a EMSA-PKCS1-v1_5 padding (See {@link http://tools.ietf.org/html/rfc4880#section-13.1.3|RFC 4880 13.1.3})
      * @param {Integer} algo Hash algorithm type used
-     * @param {String} data Data to be hashed
-     * @param {Integer} keylength Key size of the public mpi in bytes
-     * @returns {String} Hashcode with pkcs1padding as string
+     * @param {String} M message to be encoded
+     * @param {Integer} emLen intended length in octets of the encoded message
+     * @returns {String} encoded message
      */
-    encode: function(algo, data, keylength) {
-      var data2 = "";
-      data2 += String.fromCharCode(0x00);
-      data2 += String.fromCharCode(0x01);
+    encode: function(algo, M, emLen) {
       var i;
-      for (i = 0; i < (keylength - hash_headers[algo].length - 3 -
-        hash.getHashByteLength(algo)); i++)
-
-        data2 += String.fromCharCode(0xff);
-
-      data2 += String.fromCharCode(0x00);
-
-      for (i = 0; i < hash_headers[algo].length; i++)
-        data2 += String.fromCharCode(hash_headers[algo][i]);
-
-      data2 += hash.digest(algo, data);
-      return new BigInteger(util.hexstrdump(data2), 16);
-    },
-
-    /**
-     * extract the hash out of an EMSA-PKCS1-v1.5 padding (See {@link http://tools.ietf.org/html/rfc4880#section-13.1.3|RFC 4880 13.1.3})
-     * @param {String} data Hash in pkcs1 encoding
-     * @returns {String} The hash as string
-     */
-    decode: function(algo, data) {
-      var i = 0;
-      if (data.charCodeAt(0) === 0) i++;
-      else if (data.charCodeAt(0) != 1) return -1;
-      else i++;
-
-      while (data.charCodeAt(i) == 0xFF) i++;
-      if (data.charCodeAt(i++) !== 0) return -1;
-      var j = 0;
-      for (j = 0; j < hash_headers[algo].length && j + i < data.length; j++) {
-        if (data.charCodeAt(j + i) != hash_headers[algo][j]) return -1;
+      // Apply the hash function to the message M to produce a hash value H
+      var H = hash.digest(algo, M);
+      if (H.length !== hash.getHashByteLength(algo)) {
+        throw new Error('Invalid hash length');
       }
-      i += j;
-      if (data.substring(i).length < hash.getHashByteLength(algo)) return -1;
-      return data.substring(i);
+      // produce an ASN.1 DER value for the hash function used.
+      // Let T be the full hash prefix
+      var T = '';
+      for (i = 0; i < hash_headers[algo].length; i++) {
+        T += String.fromCharCode(hash_headers[algo][i]);
+      }
+      // add hash value to prefix
+      T += H;
+      // and let tLen be the length in octets of T
+      var tLen = T.length;
+      if (emLen < tLen + 11) {
+        throw new Error('Intended encoded message length too short');
+      }
+      // an octet string PS consisting of emLen - tLen - 3 octets with hexadecimal value 0xFF
+      // The length of PS will be at least 8 octets
+      var PS = '';
+      for (i = 0; i < (emLen - tLen - 3); i++) {
+        PS += String.fromCharCode(0xff);
+      }
+      // Concatenate PS, the hash prefix T, and other padding to form the
+      // encoded message EM as EM = 0x00 || 0x01 || PS || 0x00 || T.
+      var EM = String.fromCharCode(0x00) +
+               String.fromCharCode(0x01) +
+               PS +
+               String.fromCharCode(0x00) +
+               T;
+      return new BigInteger(util.hexstrdump(EM), 16);
     }
   }
 };
diff --git a/src/crypto/signature.js b/src/crypto/signature.js
index 664329be..59ea6142 100644
--- a/src/crypto/signature.js
+++ b/src/crypto/signature.js
@@ -19,8 +19,6 @@ module.exports = {
    * @return {Boolean} true if signature (sig_data was equal to data over hash)
    */
   verify: function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) {
-    var calc_hash = hashModule.digest(hash_algo, data);
-    var dopublic;
 
     switch (algo) {
       case 1:
@@ -31,15 +29,12 @@ module.exports = {
         // RSA Sign-Only [HAC]
         var rsa = new publicKey.rsa();
         var n = publickey_MPIs[0].toBigInteger();
+        var k = publickey_MPIs[0].byteLength();
         var e = publickey_MPIs[1].toBigInteger();
-        var x = msg_MPIs[0].toBigInteger();
-        dopublic = rsa.verify(x, e, n);
-        var hash = pkcs1.emsa.decode(hash_algo, dopublic.toMPI().substring(2));
-        if (hash == -1) {
-          throw new Error('PKCS1 padding in message or key incorrect. Aborting...');
-        }
-        return hash == calc_hash;
-
+        var m = msg_MPIs[0].toBigInteger();
+        var EM = rsa.verify(m, e, n);
+        var EM2 = pkcs1.emsa.encode(hash_algo, data, k);
+        return EM.compareTo(EM2) === 0;
       case 16:
         // Elgamal (Encrypt-Only) [ELGAMAL] [HAC]
         throw new Error("signing with Elgamal is not defined in the OpenPGP standard.");
@@ -53,7 +48,7 @@ module.exports = {
         var g = publickey_MPIs[2].toBigInteger();
         var y = publickey_MPIs[3].toBigInteger();
         var m = data;
-        dopublic = dsa.verify(hash_algo, s1, s2, m, p, q, g, y);
+        var dopublic = dsa.verify(hash_algo, s1, s2, m, p, q, g, y);
         return dopublic.compareTo(s1) === 0;
       default:
         throw new Error('Invalid signature algorithm.');