You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
7.7 KiB
241 lines
7.7 KiB
/** |
|
* Javascript implementation of PKCS#1 PSS signature padding. |
|
* |
|
* @author Stefan Siegl |
|
* |
|
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de> |
|
*/ |
|
var forge = require('./forge'); |
|
require('./random'); |
|
require('./util'); |
|
|
|
// shortcut for PSS API |
|
var pss = module.exports = forge.pss = forge.pss || {}; |
|
|
|
/** |
|
* Creates a PSS signature scheme object. |
|
* |
|
* There are several ways to provide a salt for encoding: |
|
* |
|
* 1. Specify the saltLength only and the built-in PRNG will generate it. |
|
* 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that |
|
* will be used. |
|
* 3. Specify the salt itself as a forge.util.ByteBuffer. |
|
* |
|
* @param options the options to use: |
|
* md the message digest object to use, a forge md instance. |
|
* mgf the mask generation function to use, a forge mgf instance. |
|
* [saltLength] the length of the salt in octets. |
|
* [prng] the pseudo-random number generator to use to produce a salt. |
|
* [salt] the salt to use when encoding. |
|
* |
|
* @return a signature scheme object. |
|
*/ |
|
pss.create = function(options) { |
|
// backwards compatibility w/legacy args: hash, mgf, sLen |
|
if(arguments.length === 3) { |
|
options = { |
|
md: arguments[0], |
|
mgf: arguments[1], |
|
saltLength: arguments[2] |
|
}; |
|
} |
|
|
|
var hash = options.md; |
|
var mgf = options.mgf; |
|
var hLen = hash.digestLength; |
|
|
|
var salt_ = options.salt || null; |
|
if(typeof salt_ === 'string') { |
|
// assume binary-encoded string |
|
salt_ = forge.util.createBuffer(salt_); |
|
} |
|
|
|
var sLen; |
|
if('saltLength' in options) { |
|
sLen = options.saltLength; |
|
} else if(salt_ !== null) { |
|
sLen = salt_.length(); |
|
} else { |
|
throw new Error('Salt length not specified or specific salt not given.'); |
|
} |
|
|
|
if(salt_ !== null && salt_.length() !== sLen) { |
|
throw new Error('Given salt length does not match length of given salt.'); |
|
} |
|
|
|
var prng = options.prng || forge.random; |
|
|
|
var pssobj = {}; |
|
|
|
/** |
|
* Encodes a PSS signature. |
|
* |
|
* This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1. |
|
* |
|
* @param md the message digest object with the hash to sign. |
|
* @param modsBits the length of the RSA modulus in bits. |
|
* |
|
* @return the encoded message as a binary-encoded string of length |
|
* ceil((modBits - 1) / 8). |
|
*/ |
|
pssobj.encode = function(md, modBits) { |
|
var i; |
|
var emBits = modBits - 1; |
|
var emLen = Math.ceil(emBits / 8); |
|
|
|
/* 2. Let mHash = Hash(M), an octet string of length hLen. */ |
|
var mHash = md.digest().getBytes(); |
|
|
|
/* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */ |
|
if(emLen < hLen + sLen + 2) { |
|
throw new Error('Message is too long to encrypt.'); |
|
} |
|
|
|
/* 4. Generate a random octet string salt of length sLen; if sLen = 0, |
|
* then salt is the empty string. */ |
|
var salt; |
|
if(salt_ === null) { |
|
salt = prng.getBytesSync(sLen); |
|
} else { |
|
salt = salt_.bytes(); |
|
} |
|
|
|
/* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */ |
|
var m_ = new forge.util.ByteBuffer(); |
|
m_.fillWithByte(0, 8); |
|
m_.putBytes(mHash); |
|
m_.putBytes(salt); |
|
|
|
/* 6. Let H = Hash(M'), an octet string of length hLen. */ |
|
hash.start(); |
|
hash.update(m_.getBytes()); |
|
var h = hash.digest().getBytes(); |
|
|
|
/* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2 |
|
* zero octets. The length of PS may be 0. */ |
|
var ps = new forge.util.ByteBuffer(); |
|
ps.fillWithByte(0, emLen - sLen - hLen - 2); |
|
|
|
/* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length |
|
* emLen - hLen - 1. */ |
|
ps.putByte(0x01); |
|
ps.putBytes(salt); |
|
var db = ps.getBytes(); |
|
|
|
/* 9. Let dbMask = MGF(H, emLen - hLen - 1). */ |
|
var maskLen = emLen - hLen - 1; |
|
var dbMask = mgf.generate(h, maskLen); |
|
|
|
/* 10. Let maskedDB = DB \xor dbMask. */ |
|
var maskedDB = ''; |
|
for(i = 0; i < maskLen; i++) { |
|
maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i)); |
|
} |
|
|
|
/* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in |
|
* maskedDB to zero. */ |
|
var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF; |
|
maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) + |
|
maskedDB.substr(1); |
|
|
|
/* 12. Let EM = maskedDB || H || 0xbc. |
|
* 13. Output EM. */ |
|
return maskedDB + h + String.fromCharCode(0xbc); |
|
}; |
|
|
|
/** |
|
* Verifies a PSS signature. |
|
* |
|
* This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2. |
|
* |
|
* @param mHash the message digest hash, as a binary-encoded string, to |
|
* compare against the signature. |
|
* @param em the encoded message, as a binary-encoded string |
|
* (RSA decryption result). |
|
* @param modsBits the length of the RSA modulus in bits. |
|
* |
|
* @return true if the signature was verified, false if not. |
|
*/ |
|
pssobj.verify = function(mHash, em, modBits) { |
|
var i; |
|
var emBits = modBits - 1; |
|
var emLen = Math.ceil(emBits / 8); |
|
|
|
/* c. Convert the message representative m to an encoded message EM |
|
* of length emLen = ceil((modBits - 1) / 8) octets, where modBits |
|
* is the length in bits of the RSA modulus n */ |
|
em = em.substr(-emLen); |
|
|
|
/* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */ |
|
if(emLen < hLen + sLen + 2) { |
|
throw new Error('Inconsistent parameters to PSS signature verification.'); |
|
} |
|
|
|
/* 4. If the rightmost octet of EM does not have hexadecimal value |
|
* 0xbc, output "inconsistent" and stop. */ |
|
if(em.charCodeAt(emLen - 1) !== 0xbc) { |
|
throw new Error('Encoded message does not end in 0xBC.'); |
|
} |
|
|
|
/* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and |
|
* let H be the next hLen octets. */ |
|
var maskLen = emLen - hLen - 1; |
|
var maskedDB = em.substr(0, maskLen); |
|
var h = em.substr(maskLen, hLen); |
|
|
|
/* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in |
|
* maskedDB are not all equal to zero, output "inconsistent" and stop. */ |
|
var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF; |
|
if((maskedDB.charCodeAt(0) & mask) !== 0) { |
|
throw new Error('Bits beyond keysize not zero as expected.'); |
|
} |
|
|
|
/* 7. Let dbMask = MGF(H, emLen - hLen - 1). */ |
|
var dbMask = mgf.generate(h, maskLen); |
|
|
|
/* 8. Let DB = maskedDB \xor dbMask. */ |
|
var db = ''; |
|
for(i = 0; i < maskLen; i++) { |
|
db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i)); |
|
} |
|
|
|
/* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet |
|
* in DB to zero. */ |
|
db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1); |
|
|
|
/* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero |
|
* or if the octet at position emLen - hLen - sLen - 1 (the leftmost |
|
* position is "position 1") does not have hexadecimal value 0x01, |
|
* output "inconsistent" and stop. */ |
|
var checkLen = emLen - hLen - sLen - 2; |
|
for(i = 0; i < checkLen; i++) { |
|
if(db.charCodeAt(i) !== 0x00) { |
|
throw new Error('Leftmost octets not zero as expected'); |
|
} |
|
} |
|
|
|
if(db.charCodeAt(checkLen) !== 0x01) { |
|
throw new Error('Inconsistent PSS signature, 0x01 marker not found'); |
|
} |
|
|
|
/* 11. Let salt be the last sLen octets of DB. */ |
|
var salt = db.substr(-sLen); |
|
|
|
/* 12. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */ |
|
var m_ = new forge.util.ByteBuffer(); |
|
m_.fillWithByte(0, 8); |
|
m_.putBytes(mHash); |
|
m_.putBytes(salt); |
|
|
|
/* 13. Let H' = Hash(M'), an octet string of length hLen. */ |
|
hash.start(); |
|
hash.update(m_.getBytes()); |
|
var h_ = hash.digest().getBytes(); |
|
|
|
/* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */ |
|
return h === h_; |
|
}; |
|
|
|
return pssobj; |
|
};
|
|
|