/**
|
* PSS signature scheme
|
*/
|
|
var BigInteger = require('../libs/jsbn');
|
var crypt = require('crypto');
|
|
module.exports = {
|
isEncryption: false,
|
isSignature: true
|
};
|
|
var DEFAULT_HASH_FUNCTION = 'sha1';
|
var DEFAULT_SALT_LENGTH = 20;
|
|
module.exports.makeScheme = function (key, options) {
|
var OAEP = require('./schemes').pkcs1_oaep;
|
|
/**
|
* @param key
|
* @param options
|
* options [Object] An object that contains the following keys that specify certain options for encoding.
|
* └>signingSchemeOptions
|
* ├>hash [String] Hash function to use when encoding and generating masks. Must be a string accepted by node's crypto.createHash function. (default = "sha1")
|
* ├>mgf [function] The mask generation function to use when encoding. (default = mgf1SHA1)
|
* └>sLen [uint] The length of the salt to generate. (default = 20)
|
* @constructor
|
*/
|
function Scheme(key, options) {
|
this.key = key;
|
this.options = options;
|
}
|
|
Scheme.prototype.sign = function (buffer) {
|
var mHash = crypt.createHash(this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION);
|
mHash.update(buffer);
|
|
var encoded = this.emsa_pss_encode(mHash.digest(), this.key.keySize - 1);
|
return this.key.$doPrivate(new BigInteger(encoded)).toBuffer(this.key.encryptedDataLength);
|
};
|
|
Scheme.prototype.verify = function (buffer, signature, signature_encoding) {
|
if (signature_encoding) {
|
signature = Buffer.from(signature, signature_encoding);
|
}
|
signature = new BigInteger(signature);
|
|
var emLen = Math.ceil((this.key.keySize - 1) / 8);
|
var m = this.key.$doPublic(signature).toBuffer(emLen);
|
|
var mHash = crypt.createHash(this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION);
|
mHash.update(buffer);
|
|
return this.emsa_pss_verify(mHash.digest(), m, this.key.keySize - 1);
|
};
|
|
/*
|
* https://tools.ietf.org/html/rfc3447#section-9.1.1
|
*
|
* mHash [Buffer] Hashed message to encode
|
* emBits [uint] Maximum length of output in bits. Must be at least 8hLen + 8sLen + 9 (hLen = Hash digest length in bytes | sLen = length of salt in bytes)
|
* @returns {Buffer} The encoded message
|
*/
|
Scheme.prototype.emsa_pss_encode = function (mHash, emBits) {
|
var hash = this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION;
|
var mgf = this.options.signingSchemeOptions.mgf || OAEP.eme_oaep_mgf1;
|
var sLen = this.options.signingSchemeOptions.saltLength || DEFAULT_SALT_LENGTH;
|
|
var hLen = OAEP.digestLength[hash];
|
var emLen = Math.ceil(emBits / 8);
|
|
if (emLen < hLen + sLen + 2) {
|
throw new Error("Output length passed to emBits(" + emBits + ") is too small for the options " +
|
"specified(" + hash + ", " + sLen + "). To fix this issue increase the value of emBits. (minimum size: " +
|
(8 * hLen + 8 * sLen + 9) + ")"
|
);
|
}
|
|
var salt = crypt.randomBytes(sLen);
|
|
var Mapostrophe = Buffer.alloc(8 + hLen + sLen);
|
Mapostrophe.fill(0, 0, 8);
|
mHash.copy(Mapostrophe, 8);
|
salt.copy(Mapostrophe, 8 + mHash.length);
|
|
var H = crypt.createHash(hash);
|
H.update(Mapostrophe);
|
H = H.digest();
|
|
var PS = Buffer.alloc(emLen - salt.length - hLen - 2);
|
PS.fill(0);
|
|
var DB = Buffer.alloc(PS.length + 1 + salt.length);
|
PS.copy(DB);
|
DB[PS.length] = 0x01;
|
salt.copy(DB, PS.length + 1);
|
|
var dbMask = mgf(H, DB.length, hash);
|
|
// XOR DB and dbMask together
|
var maskedDB = Buffer.alloc(DB.length);
|
for (var i = 0; i < dbMask.length; i++) {
|
maskedDB[i] = DB[i] ^ dbMask[i];
|
}
|
|
var bits = 8 * emLen - emBits;
|
var mask = 255 ^ (255 >> 8 - bits << 8 - bits);
|
maskedDB[0] = maskedDB[0] & mask;
|
|
var EM = Buffer.alloc(maskedDB.length + H.length + 1);
|
maskedDB.copy(EM, 0);
|
H.copy(EM, maskedDB.length);
|
EM[EM.length - 1] = 0xbc;
|
|
return EM;
|
};
|
|
/*
|
* https://tools.ietf.org/html/rfc3447#section-9.1.2
|
*
|
* mHash [Buffer] Hashed message
|
* EM [Buffer] Signature
|
* emBits [uint] Length of EM in bits. Must be at least 8hLen + 8sLen + 9 to be a valid signature. (hLen = Hash digest length in bytes | sLen = length of salt in bytes)
|
* @returns {Boolean} True if signature(EM) matches message(M)
|
*/
|
Scheme.prototype.emsa_pss_verify = function (mHash, EM, emBits) {
|
var hash = this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION;
|
var mgf = this.options.signingSchemeOptions.mgf || OAEP.eme_oaep_mgf1;
|
var sLen = this.options.signingSchemeOptions.saltLength || DEFAULT_SALT_LENGTH;
|
|
var hLen = OAEP.digestLength[hash];
|
var emLen = Math.ceil(emBits / 8);
|
|
if (emLen < hLen + sLen + 2 || EM[EM.length - 1] != 0xbc) {
|
return false;
|
}
|
|
var DB = Buffer.alloc(emLen - hLen - 1);
|
EM.copy(DB, 0, 0, emLen - hLen - 1);
|
|
var mask = 0;
|
for (var i = 0, bits = 8 * emLen - emBits; i < bits; i++) {
|
mask |= 1 << (7 - i);
|
}
|
|
if ((DB[0] & mask) !== 0) {
|
return false;
|
}
|
|
var H = EM.slice(emLen - hLen - 1, emLen - 1);
|
var dbMask = mgf(H, DB.length, hash);
|
|
// Unmask DB
|
for (i = 0; i < DB.length; i++) {
|
DB[i] ^= dbMask[i];
|
}
|
|
bits = 8 * emLen - emBits;
|
mask = 255 ^ (255 >> 8 - bits << 8 - bits);
|
DB[0] = DB[0] & mask;
|
|
// Filter out padding
|
for (i = 0; DB[i] === 0 && i < DB.length; i++);
|
if (DB[i] != 1) {
|
return false;
|
}
|
|
var salt = DB.slice(DB.length - sLen);
|
|
var Mapostrophe = Buffer.alloc(8 + hLen + sLen);
|
Mapostrophe.fill(0, 0, 8);
|
mHash.copy(Mapostrophe, 8);
|
salt.copy(Mapostrophe, 8 + mHash.length);
|
|
var Hapostrophe = crypt.createHash(hash);
|
Hapostrophe.update(Mapostrophe);
|
Hapostrophe = Hapostrophe.digest();
|
|
return H.toString("hex") === Hapostrophe.toString("hex");
|
};
|
|
return new Scheme(key, options);
|
};
|