From 357d49f7e9fa3268bf39fdda5a45feba898dfd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= 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.');