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.

This commit is contained in:
Thomas Oberndörfer 2014-03-26 18:04:58 +01:00
parent 28e7a80eba
commit 357d49f7e9
4 changed files with 52 additions and 64 deletions

View File

@ -85,7 +85,7 @@ function mixOneRound(a, b, c, d, e, x, s, roundNumber) {
break; break;
default: default:
document.write("Bogus round number"); throw new Error("Bogus round number");
break; break;
} }

View File

@ -87,7 +87,7 @@ var jsSHA = (function() {
if (!isNaN(num)) { if (!isNaN(num)) {
bin[i >> 3] |= num << (24 - (4 * (i % 8))); bin[i >> 3] |= num << (24 - (4 * (i % 8)));
} else { } else {
return "INVALID HEX STRING"; throw new Error("INVALID HEX STRING");
} }
} }
@ -870,7 +870,7 @@ var jsSHA = (function() {
H[7].highOrder, H[7].lowOrder]; H[7].highOrder, H[7].lowOrder];
default: default:
/* This should never be reached */ /* 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 */ /* Convert the input string into the correct type */
if ("HEX" === inputFormat) { if ("HEX" === inputFormat) {
if (0 !== (srcString.length % 2)) { 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.strBinLen = srcString.length * 4;
this.strToHash = hex2binb(srcString); this.strToHash = hex2binb(srcString);
@ -905,7 +905,7 @@ var jsSHA = (function() {
this.strBinLen = srcString.length * charSize; this.strBinLen = srcString.length * charSize;
this.strToHash = str2binb(srcString); this.strToHash = str2binb(srcString);
} else { } else {
return "UNKNOWN TEXT INPUT TYPE"; throw new Error("UNKNOWN TEXT INPUT TYPE");
} }
}; };
@ -934,7 +934,7 @@ var jsSHA = (function() {
formatFunc = binb2str; formatFunc = binb2str;
break; break;
default: default:
return "FORMAT NOT RECOGNIZED"; throw new Error("FORMAT NOT RECOGNIZED");
} }
switch (variant) { switch (variant) {
@ -964,7 +964,7 @@ var jsSHA = (function() {
} }
return formatFunc(this.sha512); return formatFunc(this.sha512);
default: default:
return "HASH NOT RECOGNIZED"; throw new Error("HASH NOT RECOGNIZED");
} }
}, },
@ -998,7 +998,7 @@ var jsSHA = (function() {
formatFunc = binb2str; formatFunc = binb2str;
break; break;
default: default:
return "FORMAT NOT RECOGNIZED"; throw new Error("FORMAT NOT RECOGNIZED");
} }
/* Validate the hash variant selection and set needed variables */ /* Validate the hash variant selection and set needed variables */
@ -1024,14 +1024,14 @@ var jsSHA = (function() {
hashBitSize = 512; hashBitSize = 512;
break; break;
default: default:
return "HASH NOT RECOGNIZED"; throw new Error("HASH NOT RECOGNIZED");
} }
/* Validate input format selection */ /* Validate input format selection */
if ("HEX" === inputFormat) { if ("HEX" === inputFormat) {
/* Nibbles must come in pairs */ /* Nibbles must come in pairs */
if (0 !== (key.length % 2)) { if (0 !== (key.length % 2)) {
return "KEY MUST BE IN BYTE INCREMENTS"; throw new Error("KEY MUST BE IN BYTE INCREMENTS");
} }
keyToUse = hex2binb(key); keyToUse = hex2binb(key);
keyBinLen = key.length * 4; keyBinLen = key.length * 4;
@ -1039,7 +1039,7 @@ var jsSHA = (function() {
keyToUse = str2binb(key); keyToUse = str2binb(key);
keyBinLen = key.length * charSize; keyBinLen = key.length * charSize;
} else { } else {
return "UNKNOWN KEY INPUT TYPE"; throw new Error("UNKNOWN KEY INPUT TYPE");
} }
/* These are used multiple times, calculate and store them */ /* These are used multiple times, calculate and store them */

View File

@ -91,7 +91,6 @@ module.exports = {
result += message; result += message;
return result; 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}) * 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 * @param {String} message EME-PKCS1 padded message
@ -110,53 +109,47 @@ module.exports = {
}, },
emsa: { 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}) * 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 {Integer} algo Hash algorithm type used
* @param {String} data Data to be hashed * @param {String} M message to be encoded
* @param {Integer} keylength Key size of the public mpi in bytes * @param {Integer} emLen intended length in octets of the encoded message
* @returns {String} Hashcode with pkcs1padding as string * @returns {String} encoded message
*/ */
encode: function(algo, data, keylength) { encode: function(algo, M, emLen) {
var data2 = "";
data2 += String.fromCharCode(0x00);
data2 += String.fromCharCode(0x01);
var i; var i;
for (i = 0; i < (keylength - hash_headers[algo].length - 3 - // Apply the hash function to the message M to produce a hash value H
hash.getHashByteLength(algo)); i++) var H = hash.digest(algo, M);
if (H.length !== hash.getHashByteLength(algo)) {
data2 += String.fromCharCode(0xff); throw new Error('Invalid hash length');
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;
} }
i += j; // produce an ASN.1 DER value for the hash function used.
if (data.substring(i).length < hash.getHashByteLength(algo)) return -1; // Let T be the full hash prefix
return data.substring(i); 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);
} }
} }
}; };

View File

@ -19,8 +19,6 @@ module.exports = {
* @return {Boolean} true if signature (sig_data was equal to data over hash) * @return {Boolean} true if signature (sig_data was equal to data over hash)
*/ */
verify: function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) { verify: function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) {
var calc_hash = hashModule.digest(hash_algo, data);
var dopublic;
switch (algo) { switch (algo) {
case 1: case 1:
@ -31,15 +29,12 @@ module.exports = {
// RSA Sign-Only [HAC] // RSA Sign-Only [HAC]
var rsa = new publicKey.rsa(); var rsa = new publicKey.rsa();
var n = publickey_MPIs[0].toBigInteger(); var n = publickey_MPIs[0].toBigInteger();
var k = publickey_MPIs[0].byteLength();
var e = publickey_MPIs[1].toBigInteger(); var e = publickey_MPIs[1].toBigInteger();
var x = msg_MPIs[0].toBigInteger(); var m = msg_MPIs[0].toBigInteger();
dopublic = rsa.verify(x, e, n); var EM = rsa.verify(m, e, n);
var hash = pkcs1.emsa.decode(hash_algo, dopublic.toMPI().substring(2)); var EM2 = pkcs1.emsa.encode(hash_algo, data, k);
if (hash == -1) { return EM.compareTo(EM2) === 0;
throw new Error('PKCS1 padding in message or key incorrect. Aborting...');
}
return hash == calc_hash;
case 16: case 16:
// Elgamal (Encrypt-Only) [ELGAMAL] [HAC] // Elgamal (Encrypt-Only) [ELGAMAL] [HAC]
throw new Error("signing with Elgamal is not defined in the OpenPGP standard."); 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 g = publickey_MPIs[2].toBigInteger();
var y = publickey_MPIs[3].toBigInteger(); var y = publickey_MPIs[3].toBigInteger();
var m = data; 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; return dopublic.compareTo(s1) === 0;
default: default:
throw new Error('Invalid signature algorithm.'); throw new Error('Invalid signature algorithm.');