blob: 1b284fc84568ebff4196773919c7ff609d312c67 [file] [log] [blame]
/**
* Javascript implementation of PKCS#1 PSS signature padding.
*
* @author Stefan Siegl
*
* Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// shortcut for PSS API
var pss = 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;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'pss';
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
var nodeJS = true;
define = function(ids, factory) {
factory(require, module);
};
} else {
// <script>
if(typeof forge === 'undefined') {
forge = {};
}
return initModule(forge);
}
}
// AMD
var deps;
var defineFunc = function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
};
var tmpDefine = define;
define = function(ids, factory) {
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
if(nodeJS) {
delete define;
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
}
define = tmpDefine;
return define.apply(null, Array.prototype.slice.call(arguments, 0));
};
define(['require', 'module', './random', './util'], function() {
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
});
})();